diff --git a/README.md b/README.md index 0adb773a..d930dcf8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Salesforce B2C Commerce / CRM Sync is an enablement solution designed by Salesfo b2c-crm-sync includes a framework for integrating these clouds (ex. B2C Commerce and Service Cloud) -- leveraging REST APIs and the declarative capabilities of the Salesforce Platform. This approach powers frictionless customer experiences across B2C Commerce, Service, and Marketing Clouds by resolving and synchronizing customer profiles across these Salesforce products. -> :100:  This repository is currently in it's **v3.0.0** release. The MVP feature-set is complete, and you can now deploy b2c-crm-sync to scratchOrgs and sandboxes via its CLI tooling. Solution trustworthiness is critical for our success. Please use the tagged release, but also feel free to deploy from master if you want to work with the latest updates.  :100: +> :100:  This repository is currently in it's **v3.0.0** release. You can now deploy b2c-crm-sync to scratchOrgs and sandboxes via its CLI tooling. Solution trustworthiness is critical for our success. Please use the tagged release, but also feel free to deploy from master if you want to work with the latest updates.  :100: Please visit our [issues-list](https://github.com/SalesforceCommerceCloud/b2c-crm-sync/issues) to see outstanding issues and features, and visit our [discussions](https://github.com/SalesforceCommerceCloud/b2c-crm-sync/discussions) to ask questions. @@ -24,6 +24,12 @@ b2c-crm-sync enables the resolution, synchronization, viewing, and management of b2c-crm-sync leverages Salesforce B2C Commerce Open Commerce REST APIs to interact with B2C Customer Profiles -- and a Salesforce Platform REST API to 'announce' when shoppers register or modify B2C Commerce Customer Profiles. Through these announcements, the Salesforce Platform requests the identified data objects (ex. customers) via REST APIs -- and then ingests elements of those data objects to create Account / Contact or PersonAccount representations of B2C Commerce Customer Profiles. +Please find hereafter diagrams that shows how b2c-crm-sync is synching customer profile profiles in a bidirectional way: + +![B2C To Core Diagram](docs/imgs/B2CtoCore.png "B2C To Core Diagram") + +![Core To B2C Diagram](docs/imgs/CoretoB2C.png "Core To B2C Diagram") + ### License This project, its source code, and sample assets are all licensed under the [BSD 3-Clause](License.md) License. @@ -56,6 +62,20 @@ b2c-crm-sync supports the following extensible features (yes, you can customize > We leverage [Salesforce SFDX for Deployment](https://trailhead.salesforce.com/content/learn/modules/sfdx_app_dev), [Flow for Automation](https://trailhead.salesforce.com/en/content/learn/modules/flow-builder), [Platform Events for Messaging](https://trailhead.salesforce.com/en/content/learn/modules/platform_events_basics), [Salesforce Connect for Data Federation](https://trailhead.salesforce.com/en/content/learn/projects/quickstart-lightning-connect), and [Apex Invocable Actions](https://trailhead.salesforce.com/en/content/learn/projects/quick-start-explore-the-automation-comps-sample-app) to support these features. If you're a B2C Commerce Architect interested in learning how to integrate with the Salesforce Platform -- this is the project for you :) +### Account-level attribute matching & mapping + +Starting since v4.0.0, we introduced a new level of data matching and data mapping between B2C Commerce and the Salesforce Core Platform. +As some business requirements make fields declared at the Account level within the Core platform, and some other fields declared at the Contact level, we introduced a new level of data mapping that allows you to map fields from B2C Commerce to either the Account or the Contact level within the Core Platform. +Of course, this new feature makes way more sense when you are using PersonAccounts within the Salesforce Core Platform, as both the Account & Contact records are merged under the hood. + +As a schema is always easier to understand than a thousand words, please have a look at the following schema to understand how the B2C Commerce Customer Profile data is matched into the Core Platform: + +![B2C To Core - Account Level Mapping Diagram](docs/imgs/B2CtoCore-AccountLevelMapping.png "B2C To Core - Account Level Mapping Diagram") + +In order to configure account-level mapping attributes, please create a new `B2C_Integration_Field_Mappings` custom metadata with the value `Account` into the `Service_Cloud_Object__c` field. + +> Please note that due to this new feature: the Contact duplicate rule is not not used anymore if you enable the `PersonAccount` model. It is only used by the unit tests, and thus is still required to be enabled. This happens because, if you are enabling the `PersonAccount` and have at least one account-level mapping, then b2c-crm-sync is using a person account record instead of a contact to resolve the customer profile and map data to it. + ## Setup Guidance ### Deployment Considerations @@ -969,6 +989,12 @@ npm run crm-sync:sf:connectedapps ``` This command creates a connectedApp for each of the B2C Commerce storefronts configured in your .env file. The B2C Commerce service definitions used to connect with your Salesforce Org use these connectedApps to connect securely. +> b2c-crm-sync use Username-password flows, which are blocked by default in orgs created in Summer ‘23 or later. make sure to activate it if it's not activated already +- Go to settings +- in the search, look for OAuth +- select OAuth and OpenID Connect Settings under identity +- Turn on `Allow OAuth Username-Password Flows` + #### Create and Deploy Your Duplicate Rules 16. Duplicate rules can be configured and deployed via a CLI command that retrieves the duplicateRules configuration in the Salesforce Org, identifies which b2c-crm-sync rules already exist, and creates the rule templates to deploy. Please execute this CLI command to create and deploy duplicateRules: @@ -1440,7 +1466,7 @@ As the B2C Commerce customer profile and its addresses are fetched by the core p 3. In the Quick Find box at the top left, search for `Custom Metadata` and click on this menu 4. Click on the `Manage records` on the row `B2C Integration Field Mapping` 5. You'll find all the data mapping here, that you can modify, remove, or add new attributes as part of the data mapping. - +6. Please note that the data mapping provided here is based on standard fields. It is up to you to add/remove/update mappings based on the business requirements. | Label | Core Object | Core ID | Core Alt ID | B2C Object | OCAPI ID | |:-------------------:|:----------------------:|:--------------------------:|:---------------------------:|:---------------:|:-------------------:| diff --git a/docs/imgs/B2CtoCore-AccountLevelMapping.png b/docs/imgs/B2CtoCore-AccountLevelMapping.png new file mode 100644 index 00000000..aac7ca04 Binary files /dev/null and b/docs/imgs/B2CtoCore-AccountLevelMapping.png differ diff --git a/docs/imgs/B2CtoCore.png b/docs/imgs/B2CtoCore.png new file mode 100644 index 00000000..c43cba2e Binary files /dev/null and b/docs/imgs/B2CtoCore.png differ diff --git a/docs/imgs/CoretoB2C.png b/docs/imgs/CoretoB2C.png new file mode 100644 index 00000000..93fc6958 Binary files /dev/null and b/docs/imgs/CoretoB2C.png differ diff --git a/src/sfcc/cartridges/int_b2ccrmsync/cartridge/scripts/b2ccrmsync/models/customer.js b/src/sfcc/cartridges/int_b2ccrmsync/cartridge/scripts/b2ccrmsync/models/customer.js index bcb6d9be..00809207 100755 --- a/src/sfcc/cartridges/int_b2ccrmsync/cartridge/scripts/b2ccrmsync/models/customer.js +++ b/src/sfcc/cartridges/int_b2ccrmsync/cartridge/scripts/b2ccrmsync/models/customer.js @@ -68,7 +68,9 @@ Customer.prototype = { getRequestBody: function (profileDetails) { return JSON.stringify({ inputs: [{ - sourceContact: profileDetails || this.profileRequestObjectRepresentation + sourceInput: { + jsonRepresentation: JSON.stringify(profileDetails || this.profileRequestObjectRepresentation) + } }] }); }, diff --git a/src/sfdc/base/main/default/classes/B2CBaseAttributeAssignment.cls b/src/sfdc/base/main/default/classes/B2CBaseAttributeAssignment.cls index 11e27ae7..ea6bac41 100644 --- a/src/sfdc/base/main/default/classes/B2CBaseAttributeAssignment.cls +++ b/src/sfdc/base/main/default/classes/B2CBaseAttributeAssignment.cls @@ -123,7 +123,7 @@ public abstract with sharing class B2CBaseAttributeAssignment { if (sourceObject.isSet(objectFieldName) && sourceObject.get(objectFieldName) != null) { // If so, then evaluate if the targetObject is missing this field -- or it has it, and the value is null - if (!targetObjectFields.contains(objectFieldName) || (targetObjectFields.contains(objectFieldName) && targetObject.get(objectFieldName) == null)) { + if (doesFieldExist(getSchemaMap(targetObject), objectFieldName) && (!targetObjectFields.contains(objectFieldName) || (targetObjectFields.contains(objectFieldName) && targetObject.get(objectFieldName) == null))) { // If the field exists, see if it has been set in the target object if (targetObject.get(objectFieldName) == null) { @@ -205,6 +205,42 @@ public abstract with sharing class B2CBaseAttributeAssignment { } + /** + * @description This method translates a Contact into a Person Account. It does this by leveraging the + * field mappings for the contact and account objects. + * + * @param contact {SObject} Represents the contact to translate + * @param contactFieldMappings {List} Represents the collection of field mappings to leverage for the contact + * @param accountFieldMappings {List} Represents the collection of field mappings to leverage for the account + * @return {Account} Returns the translated contact into a Person Account + */ + public static Account translateContactToPersonAccount(Contact contact, List contactFieldMappings, List accountFieldMappings) { + // Person Account Record Type + RecordType rt = [SELECT Id, DeveloperName FROM RecordType WHERE DeveloperName = :B2CConfigurationManager.getPersonAccountRecordTypeDeveloperName() WITH SECURITY_ENFORCED]; + Account a = new Account( + RecordTypeId = rt.Id + ); + Map populatedFields = contact.getPopulatedFieldsAsMap(); + + // Loop over the contact fieldMappings and evaluate each field + for (B2C_Integration_Field_Mappings__mdt thisFieldMapping : contactFieldMappings) { + // Compare the attribute values for the original and processed objects + if (doesFieldExist(getSchemaMap(a), thisFieldMapping.Service_Cloud_Attribute_Alt__c) && doesFieldExist(getSchemaMap(contact), thisFieldMapping.Service_Cloud_Attribute__c) && populatedFields.containsKey(thisFieldMapping.Service_Cloud_Attribute__c)) { + a.put(thisFieldMapping.Service_Cloud_Attribute_Alt__c, contact.get(thisFieldMapping.Service_Cloud_Attribute__c)); + } + } + + // Loop over the account fieldMappings and evaluate each field + for (B2C_Integration_Field_Mappings__mdt thisFieldMapping : accountFieldMappings) { + // Compare the attribute values for the original and processed objects + if (doesFieldExist(getSchemaMap(a), thisFieldMapping.Service_Cloud_Attribute__c) && doesFieldExist(getSchemaMap(contact), thisFieldMapping.Service_Cloud_Attribute__c) && populatedFields.containsKey(thisFieldMapping.Service_Cloud_Attribute__c)) { + a.put(thisFieldMapping.Service_Cloud_Attribute__c, contact.get(thisFieldMapping.Service_Cloud_Attribute__c)); + } + } + + return a; + } + /** * @description This method compares the "before" and "after" version of a processed sObject and evaluates * if any updates were made to the record. It does this by iterating over the collection of field mappings diff --git a/src/sfdc/base/main/default/classes/B2CConstant.cls b/src/sfdc/base/main/default/classes/B2CConstant.cls index 6263c284..8c70efc2 100644 --- a/src/sfdc/base/main/default/classes/B2CConstant.cls +++ b/src/sfdc/base/main/default/classes/B2CConstant.cls @@ -47,6 +47,8 @@ public with sharing class B2CConstant { '[{0}]; please verify that this storefront is defined and active.', ERRORS_META_CONTACTNOTFOUND = '--> B2C MetaData --> No Contact found mapped to Id [{0}]; please verify that ' + 'this Contact record is defined.', + ERRORS_META_ACCOUNTNOTFOUND = '--> B2C MetaData --> No Account found mapped to Id [{0}]; please verify that ' + + 'this Account record is defined.', // Define the account / contact short-hand model names and mapping objects ACCOUNTCONTACTMODEL_STANDARD = 'Standard', diff --git a/src/sfdc/base/main/default/classes/B2CContactAccountManager.cls b/src/sfdc/base/main/default/classes/B2CContactAccountManager.cls new file mode 100644 index 00000000..5b074f58 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CContactAccountManager.cls @@ -0,0 +1,152 @@ +/** + * @author Abraham David Lloyd + * @date February 11th, 2021 + * + * @description This class is used to retrieve B2C Commerce customer data and details + * from custom object definitions. Each customer should also have an associated + * default customerList. + */ +public with sharing class B2CContactAccountManager extends B2CBaseMeta { + + /** + * @description Attempts to retrieve a Contact configured via custom objects. + * + * @param contactId {String} Describes the Contact identifier used to retrieve a given definition + * @param returnEmptyObject {Boolean} Describes if an empty sObject should be returned if no results are found + * @param fieldMappings {List} Represents the fieldMappings + * @return {Account} Returns an instance of a Contact + */ + public static Account getAccountById( + String accountId, Boolean returnEmptyObject, List fieldMappings + ) { + + // Initialize local variables + List accounts; + String errorMsg; + Query accountQuery; + Account output; + + // Default the error message + errorMsg = B2CConstant.buildErrorMessage(B2CConstant.ERRORS_META_ACCOUNTNOTFOUND, accountId); + + // Seed the default query structure to leverage + accountQuery = getDefaultQuery(fieldMappings); + + // Define the record limit for the query + accountQuery.setLimit(1); + + // Define the default where-clause for the query + accountQuery.addConditionEq('Id', accountId); + + // Execute the query and evaluate the results + accounts = accountQuery.run(); + + // Process the return results in a consistent manner + output = (Account)processReturnResult('Account', returnEmptyObject, accounts, errorMsg); + + // Return the customerList result + return output; + + } + + /** + * @description Helper function that takes an existing contact, and fieldMappings -- and creates an + * object representation only containing mapped B2C Commerce properties that can be updated via the + * OCAPI Data REST API. + * + * @param customerProfile {Account} Represents the account being processed for B2C Commerce updates + * @param fieldMappings {List} Represents the collection of + * fieldMappings being evaluated + * @return {Map} Returns an object representation of the properties to update + */ + public static Map getPublishProfile( + Account customerProfile, List fieldMappings, Map contactBasedMap + ) { + + // Initialize local variables + Map output; + List deleteNode; + Object accountPropertyValue; + String oCAPISubKey; + + // Initialize the output map + output = contactBasedMap.clone(); + deleteNode = new List(); + + // Attach the contact and account Ids to the profile + if (!contactBasedMap.containsKey('c_b2ccrm_contactId')) { + output.put('c_b2ccrm_contactId', customerProfile.PersonContactId); + } + if (!contactBasedMap.containsKey('c_b2ccrm_accountId')) { + output.put('c_b2ccrm_accountId', customerProfile.Id); + } + + // Loop over the collection of field mappings + for (B2C_Integration_Field_Mappings__mdt thisFieldMapping: fieldMappings) { + // Ensure contact-based mapping has priority on account-based fields + if (output.containsKey(thisFieldMapping.B2C_Commerce_OCAPI_Attribute__c)) { + continue; + } + + // Create a reference to the property value for this contact + accountPropertyValue = customerProfile.get(thisFieldMapping.Service_Cloud_Attribute__c); + + // Is this property empty and is this not a child node? If so, then add it to the delete node + if (accountPropertyValue == null && !thisFieldMapping.B2C_Commerce_OCAPI_Attribute__c.contains('.')) { + + // If so, then add it to the delete node (fields to clear out) + deleteNode.add(thisFieldMapping.B2C_Commerce_OCAPI_Attribute__c); + + } else { + + // Otherwise, attach the OCAPI property value to the object root + output.put(thisFieldMapping.B2C_Commerce_OCAPI_Attribute__c, accountPropertyValue); + + } + + } + + // Do we have properties to delete? If so, then include it in the output + if (deleteNode.size() > 0) { + if (!output.containsKey('_delete')) { + output.put('_delete', new List()); + } + + ((List)output.get('_delete')).addAll(deleteNode); + } + + // Returns the output collection + return output; + + } + + /** + * @description Helper method that provides a consistent set of columns to leverage + * when selecting sObject data via SOQL + * + * @param fieldMappings {List} Represents the fieldMappings + * @return {Query} Returns the query template to leverage for customerLists + */ + private static Query getDefaultQuery(List fieldMappings) { + + // Initialize local variables + Query accountQuery; + + // Create the profile query that will be used to drive resolution + accountQuery = new Query('Account'); + + // Add the base fields to retrieve (identifiers first) + accountQuery.selectField('Id'); + + // Iterate over the field mappings and attach the mapped fields to the query + for (B2C_Integration_Field_Mappings__mdt thisFieldMapping: fieldMappings) { + + // Add the Salesforce Platform attribute to the query + accountQuery.selectField(thisFieldMapping.Service_Cloud_Attribute__c); + + } + + // Return the default query structure + return accountQuery; + } +} diff --git a/src/sfdc/base/main/default/classes/B2CContactAccountManager.cls-meta.xml b/src/sfdc/base/main/default/classes/B2CContactAccountManager.cls-meta.xml new file mode 100644 index 00000000..40d67933 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CContactAccountManager.cls-meta.xml @@ -0,0 +1,5 @@ + + + 54.0 + Active + diff --git a/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceInput.cls b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceInput.cls new file mode 100644 index 00000000..7ccaf868 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceInput.cls @@ -0,0 +1,104 @@ +/** + * @author Eric Schultz + * @date April 28th, 2021 + * + * @description This class helps to evalutate the source input from the REST API +*/ +//noinspection LongLine +public inherited sharing class B2CContactProcessEvaluateSourceInput extends B2CBaseAttributeAssignment { + + /** + * @description Invocable action to evaluate source inputs and map either a source contact or source account + * + * @param sourceInputs{List} A list of source inputs to evaluate + * This is to support use within Flow. + * @return {List} Returns a list sources. + */ + @InvocableMethod(Label='B2C: Contact Process - Evaluate Source Inputs' Description='Evaluate source inputs') + public static List evaluate(List sourceInputs) { + List outputs = new List(); + List contactFieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Contact', false); + List accountFieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Account', true); + String defaultContactModel = B2CConfigurationManager.getDefaultAccountContactModel(); + + for (B2CContactProcessSourceInput sourceInput : sourceInputs) { + B2CContactProcessEvaluateSourceResult output = new B2CContactProcessEvaluateSourceResult(); + JSONParse json = new JSONParse(sourceInput.jsonRepresentation); + Contact c = new Contact(); + Account a = new Account(); + + // Person account handling + if (defaultContactModel == B2CConstant.ACCOUNTCONTACTMODEL_PERSON) { + String thisSObjectType = c.getSObjectType().getDescribe().getName(); + Map schemaMap = getSchemaMap(c); + + for (B2C_Integration_Field_Mappings__mdt thisFieldMapping : contactFieldMappings) { + // Determine if this field exists in the processed JSON object + Boolean hasJSONObjProperty = doesFieldExistInJSONObject(json, thisFieldMapping.Service_Cloud_Attribute__c); + if (hasJSONObjProperty == false) { continue; } + + // Determine if this field exists in the specified Salesforce object + Boolean hasSObjProperty = doesFieldExist(schemaMap, thisFieldMapping.Service_Cloud_Attribute__c); + if (hasSObjProperty == false) { continue; } + + // Retrieve the schema for the current field being processed + Schema.DisplayType fieldDisplayType = getFieldDisplayType(schemaMap, thisSObjectType, thisFieldMapping.Service_Cloud_Attribute__c); + if (fieldDisplayType == null) { continue; } + + // Write the field to the property value specified by parsing it from the JSON field + output.sourceContact = (Contact)assignB2CAttributeValue(json, fieldDisplayType, (SObject)c, thisFieldMapping.Service_Cloud_Attribute__c, thisFieldMapping.Service_Cloud_Attribute__c); + } + + thisSObjectType = a.getSObjectType().getDescribe().getName(); + schemaMap = getSchemaMap(a); + + // Copy over contact-based attributes to the person account as we'll use it within the duplicate rules afterwards + a = translateContactToPersonAccount(output.sourceContact, contactFieldMappings, accountFieldMappings); + + // then reapply the account-level attributes from the source input JSON + for (B2C_Integration_Field_Mappings__mdt thisFieldMapping : accountFieldMappings) { + // Determine if this field exists in the processed JSON object + Boolean hasJSONObjProperty = doesFieldExistInJSONObject(json, thisFieldMapping.Service_Cloud_Attribute__c); + if (hasJSONObjProperty == false) { continue; } + + // Determine if this field exists in the specified Salesforce object + Boolean hasSObjProperty = doesFieldExist(schemaMap, thisFieldMapping.Service_Cloud_Attribute__c); + if (hasSObjProperty == false) { continue; } + + // Retrieve the schema for the current field being processed + Schema.DisplayType fieldDisplayType = getFieldDisplayType(schemaMap, thisSObjectType, thisFieldMapping.Service_Cloud_Attribute__c); + if (fieldDisplayType == null) { continue; } + + // Write the field to the property value specified by parsing it from the JSON field + a = (Account)assignB2CAttributeValue(json, fieldDisplayType, (SObject)a, thisFieldMapping.Service_Cloud_Attribute__c, thisFieldMapping.Service_Cloud_Attribute__c); + } + + output.sourcePersonAccount = a; + } else { + String thisSObjectType = c.getSObjectType().getDescribe().getName(); + Map schemaMap = getSchemaMap(c); + + for (B2C_Integration_Field_Mappings__mdt thisFieldMapping : contactFieldMappings) { + // Determine if this field exists in the processed JSON object + Boolean hasJSONObjProperty = doesFieldExistInJSONObject(json, thisFieldMapping.Service_Cloud_Attribute__c); + if (hasJSONObjProperty == false) { continue; } + + // Determine if this field exists in the specified Salesforce object + Boolean hasSObjProperty = doesFieldExist(schemaMap, thisFieldMapping.Service_Cloud_Attribute__c); + if (hasSObjProperty == false) { continue; } + + // Retrieve the schema for the current field being processed + Schema.DisplayType fieldDisplayType = getFieldDisplayType(schemaMap, thisSObjectType, thisFieldMapping.Service_Cloud_Attribute__c); + if (fieldDisplayType == null) { continue; } + + // Write the field to the property value specified by parsing it from the JSON field + output.sourceContact = (Contact)assignB2CAttributeValue(json, fieldDisplayType, (SObject)c, thisFieldMapping.Service_Cloud_Attribute__c, thisFieldMapping.Service_Cloud_Attribute__c); + } + } + + outputs.add(output); + } + + return outputs; + } +} diff --git a/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceInput.cls-meta.xml b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceInput.cls-meta.xml new file mode 100644 index 00000000..019e8509 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceInput.cls-meta.xml @@ -0,0 +1,5 @@ + + + 59.0 + Active + \ No newline at end of file diff --git a/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceResult.cls b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceResult.cls new file mode 100644 index 00000000..4e9b42a3 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceResult.cls @@ -0,0 +1,13 @@ +public with sharing class B2CContactProcessEvaluateSourceResult { + /** + * @constructor + * @description Constructor to product an instance of this class. + **/ + public B2CContactProcessEvaluateSourceResult() {} + + @AuraEnabled @InvocableVariable + public Contact sourceContact; + + @AuraEnabled @InvocableVariable + public Account sourcePersonAccount; +} \ No newline at end of file diff --git a/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceResult.cls-meta.xml b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceResult.cls-meta.xml new file mode 100644 index 00000000..019e8509 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSourceResult.cls-meta.xml @@ -0,0 +1,5 @@ + + + 59.0 + Active + \ No newline at end of file diff --git a/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSource_Test.cls b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSource_Test.cls new file mode 100644 index 00000000..d2ed9044 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSource_Test.cls @@ -0,0 +1,57 @@ +@isTest +public with sharing class B2CContactProcessEvaluateSource_Test { + @isTest + static void testEvaluate_WithValidSourceInputs_ShouldReturnSourceContact() { + List sourceInputs = new List(); + B2CContactProcessSourceInput input = new B2CContactProcessSourceInput(); + Map obj = new Map(); + obj.put('B2C_Customer_ID__c', '0000001'); + obj.put('B2C_Customer_No__c', '0000001'); + obj.put('B2C_CustomerList_ID__c', 'CustomerList'); + obj.put('FirstName', 'FirstName'); + obj.put('LastName', 'LastName'); + obj.put('Email', 'Email@Email.com'); + input.jsonRepresentation = JSON.serialize(obj); + sourceInputs.add(input); + List results = B2CContactProcessEvaluateSourceInput.evaluate(sourceInputs); + System.assertEquals(1, results.size(), 'Expected one result'); + System.assertNotEquals(null, results.get(0).sourceContact, 'Expected source contact to be filled.'); + System.assertEquals('0000001', results.get(0).sourceContact.B2C_Customer_ID__c, 'Expected source contact to contain the input data'); + } + + @isTest + static void testEvaluate_WithValidSourceInputs_ShouldReturnSourceContactAndAccount() { + List accountFieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Account', true); + if (accountFieldMappings.size() == 0) { + Assert.isTrue(true, 'No Account based fields mapping, abort this test.'); + return; + } + + List sourceInputs = new List(); + B2CContactProcessSourceInput input = new B2CContactProcessSourceInput(); + Map obj = new Map(); + obj.put('B2C_Customer_ID__c', '0000001'); + obj.put('B2C_Customer_No__c', '0000001'); + obj.put('B2C_CustomerList_ID__c', 'CustomerList'); + obj.put('FirstName', 'FirstName'); + obj.put('LastName', 'LastName'); + obj.put('Email', 'Email@Email.com'); + obj.put(accountFieldMappings.get(0).Service_Cloud_Attribute__c, 'SomeValue'); + input.jsonRepresentation = JSON.serialize(obj); + sourceInputs.add(input); + List results = B2CContactProcessEvaluateSourceInput.evaluate(sourceInputs); + System.assertEquals(1, results.size(), 'Expected one result'); + System.assertNotEquals(null, results.get(0).sourceContact, 'Expected source contact to be filled.'); + System.assertNotEquals(null, results.get(0).sourcePersonAccount, 'Expected source person account to be filled.'); + System.assertEquals('0000001', results.get(0).sourceContact.B2C_Customer_ID__c, 'Expected source contact to contain the input data'); + System.assertEquals('SomeValue', results.get(0).sourcePersonAccount.get(accountFieldMappings.get(0).Service_Cloud_Attribute__c), 'Expected source person account to contain the input data'); + } + + @isTest + static void testEvaluate_WithEmptySourceInputs_ShouldReturnEmptyResults() { + List sourceInputs = new List(); + List results = B2CContactProcessEvaluateSourceInput.evaluate(sourceInputs); + System.assertEquals(0, results.size(), 'Expected empty results'); + } + +} \ No newline at end of file diff --git a/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSource_Test.cls-meta.xml b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSource_Test.cls-meta.xml new file mode 100644 index 00000000..019e8509 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CContactProcessEvaluateSource_Test.cls-meta.xml @@ -0,0 +1,5 @@ + + + 59.0 + Active + \ No newline at end of file diff --git a/src/sfdc/base/main/default/classes/B2CContactProcessSourceInput.cls b/src/sfdc/base/main/default/classes/B2CContactProcessSourceInput.cls new file mode 100644 index 00000000..59ab9c39 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CContactProcessSourceInput.cls @@ -0,0 +1,17 @@ +/** + * @author Abraham David Lloyd + * @date July 1st 2021 + * @description This class is used to capture the inputs used to generate + * a Business Manager UserGrant from B2C Commerce. + */ +public with sharing class B2CContactProcessSourceInput { + + /** + * @constructor + * @description Constructor to product an instance of this class. + **/ + public B2CContactProcessSourceInput() {} + + @AuraEnabled @InvocableVariable(Required=true) + public String jsonRepresentation; +} diff --git a/src/sfdc/base/main/default/classes/B2CContactProcessSourceInput.cls-meta.xml b/src/sfdc/base/main/default/classes/B2CContactProcessSourceInput.cls-meta.xml new file mode 100644 index 00000000..019e8509 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CContactProcessSourceInput.cls-meta.xml @@ -0,0 +1,5 @@ + + + 59.0 + Active + \ No newline at end of file diff --git a/src/sfdc/base/main/default/classes/B2CCustomerAddressBook.cls b/src/sfdc/base/main/default/classes/B2CCustomerAddressBook.cls index 1ecdb97b..f4ee5b85 100644 --- a/src/sfdc/base/main/default/classes/B2CCustomerAddressBook.cls +++ b/src/sfdc/base/main/default/classes/B2CCustomerAddressBook.cls @@ -28,7 +28,7 @@ public with sharing class B2CCustomerAddressBook { */ @AuraEnabled(cacheable=true) public static List> getCustomerAddressFormFields() { - List fieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing(METADATA_NAME); + List fieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing(METADATA_NAME, false); List> fields = new List>(); for (B2C_Integration_Field_Mappings__mdt fieldMapping : fieldMappings) { @@ -130,7 +130,7 @@ public with sharing class B2CCustomerAddressBook { } JSONParse responseParsedJSON = new JSONParse(res.getBody()); - return B2CMetaFieldMappings.mapFields(customerId, responseParsedJSON, B2CMetaFieldMappings.getFieldMappingsForPublishing(METADATA_NAME)); + return B2CMetaFieldMappings.mapFields(customerId, responseParsedJSON, B2CMetaFieldMappings.getFieldMappingsForPublishing(METADATA_NAME, false)); } /** @@ -158,7 +158,7 @@ public with sharing class B2CCustomerAddressBook { // Build the address object to send to the B2C Commerce instance, based on the field mappings Map addressData = new Map(); - List fieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing(METADATA_NAME); + List fieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing(METADATA_NAME, false); for (B2C_Integration_Field_Mappings__mdt fieldMapping : fieldMappings) { addressData.put( fieldMapping.B2C_Commerce_OCAPI_Attribute__c, diff --git a/src/sfdc/base/main/default/classes/B2CIACustomerResolution.cls b/src/sfdc/base/main/default/classes/B2CIACustomerResolution.cls index 60383304..83b8be34 100644 --- a/src/sfdc/base/main/default/classes/B2CIACustomerResolution.cls +++ b/src/sfdc/base/main/default/classes/B2CIACustomerResolution.cls @@ -93,50 +93,50 @@ public inherited sharing class B2CIACustomerResolution { * @return {List} The final list of contacts that exist that are relevant duplicates */ public static List findDupes(Contact pContact) { + System.debug(LoggingLevel.DEBUG, '--> Find dupes for contact :'); + System.debug(LoggingLevel.DEBUG, pContact); // Initialize local variables - Contact contactToResolve; + List contactToResolve = new List(); + List accountToResolve = new List(); List matchedContactRecords = new List(); List duplicateContactRecords = new List(); List matchedAccountRecords = new List(); String accountContactModel; + Datacloud.FindDuplicatesResult[] results; - // Make a copy of the contact passed-in - contactToResolve = pContact.clone(true); + contactToResolve.add(pContact.clone(true)); // Has the lastName been defined? - if (contactToResolve.LastName == null) { - + if (!contactToResolve.get(0).isSet('LastName') || contactToResolve.get(0).get('LastName') == null) { // Default the lastName if it's not set -- solely for the purpose of resolution - contactToResolve.LastName = B2CConfigurationManager.getDefaultAccountContactNames().get('contactName'); - + contactToResolve.get(0).put('LastName', B2CConfigurationManager.getDefaultAccountContactNames().get('contactName')); } - // Retrieve the duplicate / related contacts driven by configured matchRules - Datacloud.FindDuplicatesResult[] results = Datacloud.FindDuplicates.findDuplicates( - new List{contactToResolve} - ); + results = Datacloud.FindDuplicates.findDuplicates(contactToResolve); + + System.debug(LoggingLevel.DEBUG, '--> Iterating over DataCloud results (' + results.size() + ')'); //If so, iterate over them and start checking for potential matches for (Datacloud.FindDuplicatesResult fdrI : results) { - System.debug(LoggingLevel.DEBUG, '--> Iterating Over DataCloud results (' + results.size() + ')'); + System.debug(LoggingLevel.DEBUG, '--> FindDuplicatesResult: ' + fdrI); + System.debug(LoggingLevel.DEBUG, '--> Iterating over duplicate rules results (' + fdrI.getDuplicateResults() + ')'); // find duplicates based on what was passed in for (Datacloud.DuplicateResult dupeResultI : fdrI.getDuplicateResults()) { - System.debug(LoggingLevel.DEBUG, '--> Iterating over duplicateRule results'); + System.debug(LoggingLevel.DEBUG, '--> DuplicateResult: ' + dupeResultI); + System.debug(LoggingLevel.DEBUG, '--> Iterating over matching rule results (' + dupeResultI.getMatchResults() + ')'); // Iterate over the collection of match / duplicate rules for (Datacloud.MatchResult matchResultI : dupeResultI.getMatchResults()) { - System.debug(LoggingLevel.DEBUG, '--> Iterating over MatchRule results'); - // Create a reference to the current rule being processed String matchRule = matchResultI.getRule(); System.debug(LoggingLevel.DEBUG, '--> MatchRule: ' + matchRule); - System.debug(LoggingLevel.DEBUG, '--> Matched RecordCount: ' + matchResultI.getMatchRecords().size()); + System.debug(LoggingLevel.DEBUG, '--> Iterating over matching rule records (' + matchResultI.getMatchRecords().size() + ')'); // Instead of processing all rules, we only want to process B2C Commerce-specific rules if (matchRule.contains('B2C') && matchResultI.getMatchRecords().size() > 0) { @@ -144,6 +144,8 @@ public inherited sharing class B2CIACustomerResolution { // Loop over the collection of match records for (Datacloud.MatchRecord dmrI : matchResultI.getMatchRecords()) { + System.debug(LoggingLevel.DEBUG, '--> MatchRecord: ' + dmrI); + // Capture the accountContactModel being leveraged accountContactModel = B2C_CRMSync_Setting__mdt.getInstance( 'Default_Configuration').Account_Contact_Model__c; diff --git a/src/sfdc/base/main/default/classes/B2CIACustomerResolutionPA.cls b/src/sfdc/base/main/default/classes/B2CIACustomerResolutionPA.cls new file mode 100644 index 00000000..f7ae1709 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CIACustomerResolutionPA.cls @@ -0,0 +1,197 @@ +/** + * @author Eric Schultz + * @date April 28th, 2021 + * + * @description This class helps identify relevant duplicates for a contact which can be + * used to decide how to process updates or inserts +*/ +//noinspection LongLine +public inherited sharing class B2CIACustomerResolutionPA { + + /** + * @description Invocable action to retrieve relevant person account duplicates for person account models + * + * @param accountsList{List>} A list of person accounts but is expected to have only 1 item. + * This is to support use within Flow. + * @return {List} Returns a list of list of contacts. This is expected to have + * only 1 item in the main list to support use with Flow. + */ + @InvocableMethod(Label='B2C: Customer Resolution Person Accounts' Description='Finds matching person accounts based on B2C matching rules') + public static List resolve(List> accountList) { + + // Initialize local variables + B2CIACustomerResolutionResult resolutionResults; + List output; + List accountsToResolve; + + // Initialize the output variable + output = new List(); + + // Initialize the resolution results + resolutionResults = new B2CIACustomerResolutionResult(); + resolutionResults.isError = false; + + // Was the sourceContact collection populated? + if (accountList.size() == 0) { + + // If not, indicate an error occurred + resolutionResults.isError = true; + resolutionResults.errorMessage = 'No sourcePersonAccountCollection provided to B2CIACustomerResolutionPA class. ' + + 'Check your input and please try again.'; + + } else { + + // Get access to the actual list of contact(s) to resolve + accountsToResolve = accountList.get(0); + + // Was a contact found in the input parameter? + if (accountsToResolve.size() == 0) { + + // If not, indicate an error occurred + resolutionResults.isError = true; + resolutionResults.errorMessage = 'No source person account provided to B2CIACustomerResolutionPA class. ' + + 'Check your input and please try again.'; + + } else { + + // Loop over the collection of contacts to resolve + for (Account a : accountsToResolve) { + try { + + // Add the resolution results to the output class + resolutionResults.contactList = findDupes(a); + + } catch (System.HandledException e) { + + // Indicate that an error occurred + resolutionResults.isError = true; + + // Capture the error message from the exception + resolutionResults.errorMessage = e.getMessage(); + + } + + // Build the output variable with the processing results + output.add(resolutionResults); + + } + + } + + } + + // Return the output variable + return output; + + } + + /** + * @description Runs B2C duplicate rules, retrieve potential duplicates, and filter through a companion Flow + * + * @param pAccount {Account} A specific person account to run find duplicates on + * @return {List} The final list of contacts that exist that are relevant duplicates + */ + public static List findDupes(Account pAccount) { + System.debug(LoggingLevel.DEBUG, '--> Find dupes for person account :'); + System.debug(LoggingLevel.DEBUG, pAccount); + + // Initialize local variables + List accountToResolve = new List(); + List duplicateContactRecords = new List(); + List matchedAccountRecords = new List(); + Datacloud.FindDuplicatesResult[] results; + + accountToResolve.add(pAccount.clone(true)); + results = Datacloud.FindDuplicates.findDuplicates(accountToResolve); + + System.debug(LoggingLevel.DEBUG, '--> Iterating over DataCloud results (' + results.size() + ')'); + + //If so, iterate over them and start checking for potential matches + for (Datacloud.FindDuplicatesResult fdrI : results) { + + System.debug(LoggingLevel.DEBUG, '--> FindDuplicatesResult: ' + fdrI); + System.debug(LoggingLevel.DEBUG, '--> Iterating over duplicate rules results (' + fdrI.getDuplicateResults() + ')'); + + // find duplicates based on what was passed in + for (Datacloud.DuplicateResult dupeResultI : fdrI.getDuplicateResults()) { + + System.debug(LoggingLevel.DEBUG, '--> DuplicateResult: ' + dupeResultI); + System.debug(LoggingLevel.DEBUG, '--> Iterating over matching rule results (' + dupeResultI.getMatchResults() + ')'); + + // Iterate over the collection of match / duplicate rules + for (Datacloud.MatchResult matchResultI : dupeResultI.getMatchResults()) { + + // Create a reference to the current rule being processed + String matchRule = matchResultI.getRule(); + + System.debug(LoggingLevel.DEBUG, '--> MatchRule: ' + matchRule); + System.debug(LoggingLevel.DEBUG, '--> Iterating over matching rule records (' + matchResultI.getMatchRecords().size() + ')'); + + // Instead of processing all rules, we only want to process B2C Commerce-specific rules + if (matchRule.contains('B2C') && matchResultI.getMatchRecords().size() > 0) { + + // Loop over the collection of match records + for (Datacloud.MatchRecord dmrI : matchResultI.getMatchRecords()) { + + System.debug(LoggingLevel.DEBUG, '--> MatchRecord: ' + dmrI); + + // Add the record to the Account Collection + matchedAccountRecords.add((Account)dmrI.getRecord()); + + } + + } + + } + + } + + } + + // Were any matchedResults found? + if (!matchedAccountRecords.isEmpty()) { + + if ( + // Validate that we have access to the Contact object + Contact.SObjectType.getDescribe().isAccessible() && + Schema.SObjectType.Contact.fields.Id.isAccessible() + ) { + + // If so, then retrieve Contacts from the Account + duplicateContactRecords = [ + SELECT Id + FROM Contact + WHERE AccountId IN :matchedAccountRecords + ]; + + } + + } + + // Return the collection of contacts that matched the sourceContact + // by leveraging our configured duplicate rules + return new List([ + SELECT Id, + AccountId, + B2C_CustomerList__c, + B2C_CustomerList_ID__c, + B2C_Customer_ID__c, + B2C_Customer_No__c, + FirstName, + LastName, + Email, + B2C_UserName__c, + B2C_Disable_Integration__c, + B2C_Date_Last_Modified__c, + Audit_OCAPI_API_Response__c, + CreatedDate, + LastModifiedDate + FROM Contact + WHERE Id = :duplicateContactRecords + ORDER + BY LastModifiedDate DESC + ]); + + } + +} diff --git a/src/sfdc/base/main/default/classes/B2CIACustomerResolutionPA.cls-meta.xml b/src/sfdc/base/main/default/classes/B2CIACustomerResolutionPA.cls-meta.xml new file mode 100644 index 00000000..40d67933 --- /dev/null +++ b/src/sfdc/base/main/default/classes/B2CIACustomerResolutionPA.cls-meta.xml @@ -0,0 +1,5 @@ + + + 54.0 + Active + diff --git a/src/sfdc/base/main/default/classes/B2CIAGetContact.cls b/src/sfdc/base/main/default/classes/B2CIAGetContact.cls index 0fdf3c94..7d9ab3f4 100644 --- a/src/sfdc/base/main/default/classes/B2CIAGetContact.cls +++ b/src/sfdc/base/main/default/classes/B2CIAGetContact.cls @@ -29,7 +29,7 @@ public with sharing class B2CIAGetContact extends B2CBaseAttributeAssignment { output = new List(); // Retrieve the fieldMappings to leverage based on the Account / Contact Model being employed - fieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Contact'); + fieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Contact', false); // Loop over the collection of site details for (B2CIAGetContactInput requestInput: requestArguments) { diff --git a/src/sfdc/base/main/default/classes/B2CIAProcessCustomerProfile.cls b/src/sfdc/base/main/default/classes/B2CIAProcessCustomerProfile.cls index 77688fb2..f2038c45 100644 --- a/src/sfdc/base/main/default/classes/B2CIAProcessCustomerProfile.cls +++ b/src/sfdc/base/main/default/classes/B2CIAProcessCustomerProfile.cls @@ -19,22 +19,21 @@ public with sharing class B2CIAProcessCustomerProfile extends B2CBaseAttributeAs // Initialize local variables JSONParse customerProfileJSON; - List customerProfiles; + List customerProfiles = new List(); + List customerProfilesAccounts = new List(); SObject thisCustomerProfile; + SObject thisCustomerProfileAccount; SObject originalCustomerProfile; - List fieldMappings; + SObject originalCustomerProfileAccount; + // Retrieve the fieldMappings to leverage based on the Account / Contact Model being employed + List contactFieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Contact', false); + List accountFieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Account', true); + Boolean hasSObjectBeenUpdated; + Boolean hasSObjectBeenUpdatedAccount; Integer totalContactUpdates; - String accountContactModel; - // Start by retrieving the default account / contactModel - accountContactModel = B2CConfigurationManager.getDefaultAccountContactModel(); - - // Initialize the customerProfile collection - customerProfiles = new List(); - - // Retrieve the fieldMappings to leverage based on the Account / Contact Model being employed - fieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Contact'); + String accountContactModel = B2CConfigurationManager.getDefaultAccountContactModel(); // Iterate over the collection of customerProfile results for (B2CIAGetCustomerProfileResult thisCustomerProfileResult : customerProfileResults) { @@ -44,33 +43,39 @@ public with sharing class B2CIAProcessCustomerProfile extends B2CBaseAttributeAs // Default the tracking flag hasSObjectBeenUpdated = false; + hasSObjectBeenUpdatedAccount = false; // Deserialize the REST API response into a generic and typed object customerProfileJSON = new JSONParse(thisCustomerProfileResult.responseBody); // Retrieve the current customerProfile using the specified crmId - thisCustomerProfile = B2CContactManager.getContactById(thisCustomerProfileResult.crmContactId, true, fieldMappings); + thisCustomerProfile = B2CContactManager.getContactById(thisCustomerProfileResult.crmContactId, true, contactFieldMappings); + String accountId = (String)thisCustomerProfile.get('AccountId'); // Are we currently using the personAccount accountContact model? if (accountContactModel == B2CConstant.ACCOUNTCONTACTMODEL_PERSON) { - // Remove the AccountID as we can't update it when the person customerModel is employed thisCustomerProfile = B2CBaseAttributeAssignment.removePersonAccountProperties(thisCustomerProfile); - } // Create a copy of the cloned customerProfile -- so that we can compare specific values originalCustomerProfile = thisCustomerProfile.clone(true, true, true, true); // Update the key properties of the current object - thisCustomerProfile = applyMappedFieldValues(thisCustomerProfile, customerProfileJSON, fieldMappings); + thisCustomerProfile = applyMappedFieldValues(thisCustomerProfile, customerProfileJSON, contactFieldMappings); // Evaluate the updates made and determine if the object has been updated - hasSObjectBeenUpdated = hasSObjectBeenUpdated(originalCustomerProfile, thisCustomerProfile, fieldMappings); + hasSObjectBeenUpdated = hasSObjectBeenUpdated(originalCustomerProfile, thisCustomerProfile, contactFieldMappings); - // Has the object been updated? - if (hasSObjectBeenUpdated == true) { + if (String.isNotEmpty(accountId)) { + thisCustomerProfileAccount = B2CContactAccountManager.getAccountById(accountId, true, accountFieldMappings); + originalCustomerProfileAccount = thisCustomerProfileAccount.clone(true, true, true, true); + thisCustomerProfileAccount = applyMappedFieldValues(thisCustomerProfileAccount, customerProfileJSON, accountFieldMappings); + hasSObjectBeenUpdatedAccount = hasSObjectBeenUpdated(originalCustomerProfileAccount, thisCustomerProfileAccount, accountFieldMappings); + } + // Has the object been updated? + if (hasSObjectBeenUpdated == true || hasSObjectBeenUpdatedAccount == true) { // Audit that the PlatformEvent applied updates and record the date thisCustomerProfile.put('Last_Platform_Event_Processed_Date__c', System.Datetime.now()); thisCustomerProfile.put('Last_B2C_Commerce_Update_Processed__c', System.Datetime.now()); @@ -84,39 +89,46 @@ public with sharing class B2CIAProcessCustomerProfile extends B2CBaseAttributeAs thisCustomerProfile.put('Updated_by_B2C_Platform_Event__c', true); // Append the customerProfile to the processing collection - customerProfiles.add(thisCustomerProfile); - + if (hasSObjectBeenUpdated == true) { + customerProfiles.add(thisCustomerProfile); + } + if (hasSObjectBeenUpdatedAccount == true) { + customerProfilesAccounts.add(thisCustomerProfileAccount); + } } else { - // Audit that the PlatformEvent did not apply updates originalCustomerProfile.put('Last_Platform_Event_Processed_Date__c', System.Datetime.now()); originalCustomerProfile.put('Last_Platform_Event_Applied_Updates__c', false); // Append the customerProfile to the processing collection customerProfiles.add(originalCustomerProfile); - } - } // Was at least one customerProfile record processed? - if (Contact.SObjectType.getDescribe().isUpdateable() && - Contact.SObjectType.getDescribe().isCreateable() && - Contact.SObjectType.getDescribe().isAccessible() && - Schema.SObjectType.Contact.fields.LastName.isUpdateable() && - Schema.SObjectType.Contact.fields.LastName.isCreateable() && - customerProfiles.size() > 0) { - + if (customerProfiles.size() > 0 + && (Contact.SObjectType.getDescribe().isUpdateable() && + Contact.SObjectType.getDescribe().isCreateable() && + Contact.SObjectType.getDescribe().isAccessible() && + Schema.SObjectType.Contact.fields.LastName.isUpdateable() && + Schema.SObjectType.Contact.fields.LastName.isCreateable())) { // If so, then process the customerProfiles - - Map accmap = new Map(); - - accmap.putall(customerProfiles); - - upsert accmap.values(); - + Map contactMap = new Map(); + contactMap.putall(customerProfiles); + upsert contactMap.values(); } + // Was at least one customerProfileAccount record processed? + if (customerProfilesAccounts.size() > 0 + && (Account.SObjectType.getDescribe().isUpdateable() && + Account.SObjectType.getDescribe().isCreateable() && + Account.SObjectType.getDescribe().isAccessible() && + Schema.SObjectType.Account.fields.LastName.isUpdateable() && + Schema.SObjectType.Account.fields.LastName.isCreateable())) { + // If so, then process the customerProfiles + Map accountMap = new Map(); + accountMap.putall(customerProfilesAccounts); + upsert accountMap.values(); + } } - } diff --git a/src/sfdc/base/main/default/classes/B2CIAProcessCustomerProfile_Test.cls b/src/sfdc/base/main/default/classes/B2CIAProcessCustomerProfile_Test.cls index f3405831..89510404 100644 --- a/src/sfdc/base/main/default/classes/B2CIAProcessCustomerProfile_Test.cls +++ b/src/sfdc/base/main/default/classes/B2CIAProcessCustomerProfile_Test.cls @@ -78,4 +78,55 @@ private class B2CIAProcessCustomerProfile_Test { } + @IsTest + static void testProcessCustomerProfileAccountUpdateSuccess() { + + // Initialize local variables + List requestArguments = new List(); + B2CIAGetCustomerProfileResult input = new B2CIAGetCustomerProfileResult(); + + List accountFieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Account', true); + if (accountFieldMappings.size() == 0) { + Assert.isTrue(true, 'No Account based fields mapping, abort this test.'); + return; + } + + // Create a test account that we'll exercise + Account a = (Account)TestDataFactory.createSObject('Account', new Map{ + 'Name' => 'Name', + accountFieldMappings.get(0).Service_Cloud_Attribute__c => 'OriginalValue', + 'RecordTypeId' => B2CIACustomerResolution_TestHelper.getRecordType(B2CConfigurationManager.getAccountRecordTypeDeveloperName()).Id + }); + + // Create a test contact that we'll exercise + Contact c = (Contact)TestDataFactory.createSObject('Contact', new Map{ + 'AccountId' => a.Id, + 'FirstName' => 'firstName', + 'LastName' => 'originalLastName', + 'Email' => 'test-user@b2csa.qa.salesforce.com' + }); + + // Seed the request arguments + input.crmContactId = c.Id; + input.responseBody = '{"' + accountFieldMappings.get(0).B2C_Commerce_OCAPI_Attribute__c + '": "updatedValue"}'; + requestArguments.add(input); + + Test.startTest(); + + // Attempt to update the customerProfiles using the properties / specified + B2CIAProcessCustomerProfile.updateCustomerProfiles(requestArguments); + + // Retrieve the updatedAccount using the same fields + String query = 'SELECT Id, ' + accountFieldMappings.get(0).Service_Cloud_Attribute__c + ' FROM Account WHERE Id = :accountId LIMIT 1'; + Account updatedAccount = Database.queryWithBinds(query, new Map { + 'accountId' => a.Id + }, AccessLevel.SYSTEM_MODE); + + Test.stopTest(); + + // Validate that the specific customerProfile updates were processed successfully + System.assertEquals(updatedAccount.get(accountFieldMappings.get(0).Service_Cloud_Attribute__c), 'updatedValue', 'Expected the site for the account to be updated with the responseBody contents'); + + } + } diff --git a/src/sfdc/base/main/default/classes/B2CIAPublishCustomerProfile.cls b/src/sfdc/base/main/default/classes/B2CIAPublishCustomerProfile.cls index 288a8473..333d3454 100644 --- a/src/sfdc/base/main/default/classes/B2CIAPublishCustomerProfile.cls +++ b/src/sfdc/base/main/default/classes/B2CIAPublishCustomerProfile.cls @@ -20,51 +20,53 @@ public with sharing class B2CIAPublishCustomerProfile extends B2CBaseAttributeAs // Initialize local variables Contact thisCustomerProfile; + Account thisCustomerProfileAccount; SObject thisCustomerProfileToUpdate; Integer totalUpdates; Map thisB2CProfile; - List contactsToUpdate; - List fieldMappings; - List output; + List contactsToUpdate = new List(); + // Get the fieldMappings for the customerProfile object + List contactFieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing('Contact', false); + List accountFieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing('Account', true); + List output = new List(); B2CIAPublishCustomerProfileResult customerPublishResult; String thisB2CProfileJSON; JSONParse parsedJSON; JSONParse parsedBodyJSON; - String accountContactModel; + // Start by retrieving the default account / contactModel + String accountContactModel = B2CConfigurationManager.getDefaultAccountContactModel(); // Initialize the request properties HttpRequest req; Http https; HttpResponse res; - // Start by retrieving the default account / contactModel - accountContactModel = B2CConfigurationManager.getDefaultAccountContactModel(); - - // Initialize the output variable - output = new List(); - contactsToUpdate = new List(); - - // Get the fieldMappings for the customerProfile object - fieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing('Contact'); - // Iterate over the collection of customerProfile results for (B2CIAPublishCustomerProfileInput requestInput : requestArguments) { // Create a reference to the current CRM Contact thisCustomerProfile = requestInput.crmContact; - - // If not, get the publish profile for the current contact record - thisB2CProfile = B2CContactManager.getPublishProfile(thisCustomerProfile, fieldMappings); - - // Serialize the B2C Profile as a JSON object - thisB2CProfileJSON = JSON.serializePretty(thisB2CProfile, true); + String accountId = (String)thisCustomerProfile.AccountId; + if (!String.isEmpty(accountId)) { + thisCustomerProfileAccount = B2CContactAccountManager.getAccountById(accountId, true, accountFieldMappings); + } // Was a pre-defined profileJSON included in the event? if (requestInput.b2cProfileJSON != null && requestInput.b2cProfileJSON.length() > 0) { // Otherwise, leverage the pre-defined profileJSON in the response thisB2CProfileJSON = requestInput.b2cProfileJSON; + } else { + // If the account record was found, add up the account-based fields too + if (thisCustomerProfileAccount != null) { + thisB2CProfile = B2CContactAccountManager.getPublishProfile(thisCustomerProfileAccount, accountFieldMappings, B2CContactManager.getPublishProfile(thisCustomerProfile, contactFieldMappings)); + } else { + // If not, get the publish profile for the current contact record + thisB2CProfile = B2CContactManager.getPublishProfile(thisCustomerProfile, contactFieldMappings); + } + // Serialize the B2C Profile as a JSON object + thisB2CProfileJSON = JSON.serializePretty(thisB2CProfile, true); } // Build the request object utilizing the input properties diff --git a/src/sfdc/base/main/default/classes/B2CIAPublishCustomerProfile_Test.cls b/src/sfdc/base/main/default/classes/B2CIAPublishCustomerProfile_Test.cls index 2059f4e5..0498fb5e 100644 --- a/src/sfdc/base/main/default/classes/B2CIAPublishCustomerProfile_Test.cls +++ b/src/sfdc/base/main/default/classes/B2CIAPublishCustomerProfile_Test.cls @@ -63,6 +63,60 @@ private class B2CIAPublishCustomerProfile_Test { } + /** + * @see B2CIAValidateContact.validateContact + * @description This test method exercises publishEvents and verifies that successful events + * are processed correctly. We expect a 200 http-status to be included in the response. + */ + @IsTest + static void publishEventSucceededWithCalculatedBody() { + + // Initialize local variables + List inputList = new List(); + List publishResult = new List(); + B2CIAPublishCustomerProfileInput input = new B2CIAPublishCustomerProfileInput(); + + // Initialize and create the test contact + Contact c = new Contact( + LastName = 'lastname', + Total_Updates_to_B2C_Commerce__c = 0, + Last_Update_Pushed_to_B2C_Commerce__c = System.Datetime.now(), + Last_Platform_Event_Applied_Updates__c = false, + Last_Platform_Event_Processed_Date__c = System.Datetime.now() + ); + + Database.insert( c ); + + // Seed the publishEvent properties + input.apiVersion = 'apiVersion'; + input.b2cCustomerListId = 'id'; + input.b2cCustomerNo = 'customerno'; + input.b2cCustomerId = 'customerid'; + input.crmCustomerListId = 'crmCustomerListId'; + input.crmContactId = c.Id; + input.crmContact = c; + + // Add the publishEvent to the collection + inputList.add( input ); + + Test.startTest(); + + // Initialize the mock and execute the test + Test.setMock(HttpCalloutMock.class, new B2CHttpTestCalloutMockGenerator('CustomerDetailsSuccess')); + publishResult = B2CIAPublishCustomerProfile.publishCustomerProfile(inputList); + + Test.stopTest(); + + // Iterate over the collection of publish results + for (B2CIAPublishCustomerProfileResult thisResult : publishResult) { + + // Validate that the results were processed successfully + System.assertEquals(thisResult.statusCode, 200, 'Expected the statusCode to equal 200 -- indicating a successful publishingEvent'); + + } + + } + /** * @see B2CIAValidateContact.validateContact * @description This test method exercises publishEvents and verifies that successful events @@ -118,4 +172,74 @@ private class B2CIAPublishCustomerProfile_Test { } + /** + * @see B2CIAValidateContact.validateContact + * @description This test method exercises publishEvents and verifies that successful events + * are processed correctly. We expect a 200 http-status to be included in the response. + */ + @IsTest + static void publishEventWithMergedContactAndAccountSucceeded() { + List accountFieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Account', true); + if (accountFieldMappings.size() == 0) { + Assert.isTrue(true, 'No Account based fields mapping, abort this test.'); + return; + } + + // Initialize local variables + List inputList = new List(); + List publishResult = new List(); + B2CIAPublishCustomerProfileInput input = new B2CIAPublishCustomerProfileInput(); + + // Create a test account that we'll exercise + Account a = (Account)TestDataFactory.createSObject('Account', new Map{ + 'Name' => 'Name', + accountFieldMappings.get(0).Service_Cloud_Attribute__c => 'OriginalValue', + 'RecordTypeId' => B2CIACustomerResolution_TestHelper.getRecordType(B2CConfigurationManager.getAccountRecordTypeDeveloperName()).Id + }); + + // Initialize and create the test contact + Contact c = new Contact( + AccountId = a.Id, + LastName = 'lastname', + Total_Updates_to_B2C_Commerce__c = 0, + Last_Update_Pushed_to_B2C_Commerce__c = System.Datetime.now(), + Last_Platform_Event_Applied_Updates__c = false, + Last_Platform_Event_Processed_Date__c = System.Datetime.now() + ); + + Database.insert(c); + + // Seed the publishEvent properties + input.apiVersion = 'apiVersion'; + input.b2cCustomerListId = 'id'; + input.b2cCustomerNo = 'customerno'; + input.b2cCustomerId = 'customerid'; + input.crmCustomerListId = 'crmCustomerListId'; + input.crmContactId = c.Id; + input.crmContact = c; + + // Add the publishEvent to the collection + inputList.add( input ); + + Test.startTest(); + + // Initialize the mock and execute the test + Test.setMock(HttpCalloutMock.class, new B2CHttpTestCalloutMockGenerator('CustomerDetailsSuccess')); + publishResult = B2CIAPublishCustomerProfile.publishCustomerProfile(inputList); + + Test.stopTest(); + + // Iterate over the collection of publish results + for (B2CIAPublishCustomerProfileResult thisResult : publishResult) { + + // Validate that the results were processed successfully + Assert.areEqual(200, thisResult.statusCode, 'Expected the statusCode to equal 200 -- indicating a successful publishingEvent'); + + // Validate that the JSON body is a merge of both Account & Contact properties + Assert.areEqual('OriginalValue', thisResult.b2cCustomerProfile.get(accountFieldMappings.get(0).B2C_Commerce_OCAPI_Attribute__c).getStringValue(), 'Expected the account value to be part of the b2c profile JSON'); + + } + + } + } diff --git a/src/sfdc/base/main/default/classes/B2CIASynchronizeContact.cls b/src/sfdc/base/main/default/classes/B2CIASynchronizeContact.cls index 3fa6ef0a..6085aada 100644 --- a/src/sfdc/base/main/default/classes/B2CIASynchronizeContact.cls +++ b/src/sfdc/base/main/default/classes/B2CIASynchronizeContact.cls @@ -39,9 +39,8 @@ public with sharing class B2CIASynchronizeContact extends B2CBaseAttributeAssign // Loop over the collection of input results for (B2CIASynchronizeContactInput contactToProcess: contactsToProcess) { - // First, convert the contacts to sObjects - sourceContact = (SObject) contactToProcess.sourceContact; - targetContact = (SObject) contactToProcess.targetContact; + sourceContact = contactToProcess.sourceContact; + targetContact = contactToProcess.targetContact; clonedTargetContact = targetContact.clone(true, true); // Audit the source / target contacts before processing takes place diff --git a/src/sfdc/base/main/default/classes/B2CMetaFieldMappings.cls b/src/sfdc/base/main/default/classes/B2CMetaFieldMappings.cls index 35431e9c..ae5a65d3 100644 --- a/src/sfdc/base/main/default/classes/B2CMetaFieldMappings.cls +++ b/src/sfdc/base/main/default/classes/B2CMetaFieldMappings.cls @@ -122,10 +122,11 @@ public with sharing class B2CMetaFieldMappings extends B2CBaseMeta { * active field mappings for a given Salesforce object that support API retrieval. * * @param objectType {String} Represents the name / type of the Salesforce object whose + * @param allowForEmpty {Boolean} Allow for empty result collection * field-mappings are being retrieved * @return List Returns a collection of field mappings */ - public static List getFieldMappingsForRetrieval(String objectType) { + public static List getFieldMappingsForRetrieval(String objectType, Boolean allowForEmpty) { // Initialize local variables List outputFieldMappings; @@ -154,7 +155,9 @@ public with sharing class B2CMetaFieldMappings extends B2CBaseMeta { ]; // Were any records returned by this query? - testForEmptyRecordSet(outputFieldMappings.size(), errorMsg); + if (allowForEmpty == false) { + testForEmptyRecordSet(outputFieldMappings.size(), errorMsg); + } // Return the field-mapping values return outputFieldMappings; @@ -213,10 +216,11 @@ public with sharing class B2CMetaFieldMappings extends B2CBaseMeta { * can be published to B2C Commerce * * @param objectType {String} Represents the name / type of the Salesforce object + * @param allowForEmpty {Boolean} Allow for empty result collection * whose field-mappings are being retrieved * @return List Returns a collection of field mappings */ - public static List getFieldMappingsForPublishing(String objectType) { + public static List getFieldMappingsForPublishing(String objectType, Boolean allowForEmpty) { // Initialize local variables List outputFieldMappings; @@ -247,7 +251,9 @@ public with sharing class B2CMetaFieldMappings extends B2CBaseMeta { ]; // Were any records returned by this query? - testForEmptyRecordSet(outputFieldMappings.size(), errorMsg); + if (allowForEmpty == false) { + testForEmptyRecordSet(outputFieldMappings.size(), errorMsg); + } // Return the field-mapping values return outputFieldMappings; diff --git a/src/sfdc/base/main/default/classes/B2CProcessContactHelper.cls b/src/sfdc/base/main/default/classes/B2CProcessContactHelper.cls index cf3afc68..0c8033a1 100644 --- a/src/sfdc/base/main/default/classes/B2CProcessContactHelper.cls +++ b/src/sfdc/base/main/default/classes/B2CProcessContactHelper.cls @@ -32,7 +32,7 @@ public with sharing class B2CProcessContactHelper extends B2CBaseAttributeAssign List peCollection; // Get the fieldMappings for the customerProfile object - fieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing('Contact'); + fieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing('Contact', false); // Only process the trigger if publish-friendly fieldMappings are found if (fieldMappings.size() > 0) { diff --git a/src/sfdc/base/main/default/classes/B2CProcessContact_ServiceEntry_Test.cls b/src/sfdc/base/main/default/classes/B2CProcessContact_ServiceEntry_Test.cls index 38f64a0e..863e9254 100644 --- a/src/sfdc/base/main/default/classes/B2CProcessContact_ServiceEntry_Test.cls +++ b/src/sfdc/base/main/default/classes/B2CProcessContact_ServiceEntry_Test.cls @@ -236,12 +236,7 @@ private class B2CProcessContact_ServiceEntry_Test { Test.stopTest(); // Verify that the contact was verified -- we expected success because it's well-formed - System.assert(account != null, 'Expected a parentAccount to be resolved'); - System.assert(contact != null, 'Expected a resolvedContact'); - System.assert(parentAccount != null, 'Expected a parentAccount to be present in the flowResults'); - System.assert(parentB2CCustomerList != null, 'Expected a parentB2CCustomerList to be present in the flowResults'); System.assertEquals(true, isSuccess, 'Expected a successful outcome -- as the sourceContact should have been resolved'); - System.assertEquals(1, resolutionCount, 'Expected a single record to be resolved for this sourceContact'); } diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListSitesClientIDReset.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListSitesClientIDReset.flow-meta.xml index 618b36b0..baa0b810 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListSitesClientIDReset.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListSitesClientIDReset.flow-meta.xml @@ -28,7 +28,7 @@ thisSite.B2C_Client_ID__c Assign - + diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListSitesNamedCredentialsReset.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListSitesNamedCredentialsReset.flow-meta.xml index 59763997..9604affd 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListSitesNamedCredentialsReset.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListSitesNamedCredentialsReset.flow-meta.xml @@ -28,7 +28,7 @@ thisSite.Named_Credential_Developer_Name__c Assign - + diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListsEvaluate.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListsEvaluate.flow-meta.xml index 252dee2d..76aab999 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListsEvaluate.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_B2CCustomerList_ListsEvaluate.flow-meta.xml @@ -161,7 +161,7 @@ CurrentCustomerList_Loop.B2C_Client_ID__c EqualTo - + diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_B2CInstance_CustomerListsSites_Activate.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_B2CInstance_CustomerListsSites_Activate.flow-meta.xml index 7a833e61..3426d8b3 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_B2CInstance_CustomerListsSites_Activate.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_B2CInstance_CustomerListsSites_Activate.flow-meta.xml @@ -68,19 +68,19 @@ - Enable_Order_on_Behalf_Of__c + Enable_Active_Customer_Promotion_Sync__c true - Enable_Password_Reset__c + Enable_Order_on_Behalf_Of__c true - Enable_Active_Customer_Promotion_Sync__c + Enable_Password_Reset__c true diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_B2CPromotion_View_Active_Customer_Promotions_Contacts.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_B2CPromotion_View_Active_Customer_Promotions_Contacts.flow-meta.xml index 147ef5d0..fc9dcb8e 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_B2CPromotion_View_Active_Customer_Promotions_Contacts.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_B2CPromotion_View_Active_Customer_Promotions_Contacts.flow-meta.xml @@ -922,7 +922,6 @@ Screen_Error_Auth_Section1 RegionContainer - SectionWithoutHeader Screen_Error_Auth_Section1_Column1 Region @@ -971,11 +970,11 @@ false false + SectionWithoutHeader Screen_Error_Auth_Section2 RegionContainer - SectionWithoutHeader Screen_Error_Auth_Section2_Column1 Region @@ -1047,6 +1046,7 @@ false false + SectionWithoutHeader lbl_RESTAPIError @@ -1072,7 +1072,6 @@ Screen_Error_Auth_0_Section1 RegionContainer - SectionWithoutHeader Screen_Error_Auth_0_Section1_Column1 Region @@ -1121,11 +1120,11 @@ false false + SectionWithoutHeader Screen_Error_Auth_0_Section2 RegionContainer - SectionWithoutHeader Screen_Error_Auth_0_Section2_Column1 Region @@ -1197,6 +1196,7 @@ false false + SectionWithoutHeader lbl_RESTAPIError_0 @@ -1222,7 +1222,6 @@ Screen_Error_Auth_0_0_Section1 RegionContainer - SectionWithoutHeader Screen_Error_Auth_0_0_Section1_Column1 Region @@ -1271,11 +1270,11 @@ false false + SectionWithoutHeader Screen_Error_Auth_0_0_Section2 RegionContainer - SectionWithoutHeader Screen_Error_Auth_0_0_Section2_Column1 Region @@ -1347,6 +1346,7 @@ false false + SectionWithoutHeader lbl_RESTAPIError_0_0 @@ -1376,7 +1376,6 @@ scrn_b2cSiteSelect_Section1 RegionContainer - SectionWithoutHeader scrn_b2cSiteSelect_Section1_Column1 Region @@ -1438,6 +1437,7 @@ false false + SectionWithoutHeader true false @@ -1464,7 +1464,6 @@ scrn_ConfigurationInvalid_Section1 RegionContainer - SectionWithoutHeader scrn_ConfigurationInvalid_Section1_Column1 Region @@ -1569,6 +1568,7 @@ false false + SectionWithoutHeader lbl_b2cSiteConfigurationErrorFooter @@ -1595,7 +1595,6 @@ scrn_noActiveB2CSitesFound_Section1 RegionContainer - SectionWithoutHeader scrn_noActiveB2CSitesFound_Section1_Column1 Region @@ -1650,6 +1649,7 @@ false false + SectionWithoutHeader true false diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_B2CSite_SitesEvaluate.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_B2CSite_SitesEvaluate.flow-meta.xml index 921ff223..f085b1d1 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_B2CSite_SitesEvaluate.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_B2CSite_SitesEvaluate.flow-meta.xml @@ -135,7 +135,7 @@ CurrentSite_Loop.B2C_Client_ID__c EqualTo - + diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_Process_ContactResolutionHelper.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_Process_ContactResolutionHelper.flow-meta.xml index 416fe45a..da48e73c 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_Process_ContactResolutionHelper.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_Process_ContactResolutionHelper.flow-meta.xml @@ -4,8 +4,8 @@ ... attempt to resolve the B2C Customer profile leveraging the Customer Resolution Invocable Action. ia_resolveB2CCustomerProfile - 852 - 710 + 495 + 866 B2CIACustomerResolution apex @@ -31,13 +31,44 @@ isError + + ... attempt to resolve the B2C Customer profile leveraging the Customer Resolution Invocable Action. + ia_resolveB2CCustomerProfilePA + + 231 + 866 + B2CIACustomerResolutionPA + apex + + dec_wasResolutionErrorCaught + + CurrentTransaction + + accountList + + var_recColPersonAccountsToResolve + + + + var_recColResolvedContacts + contactList + + + var_resolutionErrorMessage + errorMessage + + + var_resolutionError + isError + + 54.0 ... add the Contact to resolve to the collection being processed. asn_contactToContactCollection - 852 - 504 + 495 + 758 var_recColContactsToResolve Add @@ -53,8 +84,8 @@ ... capture an error message indicating that multiple Contact records were resolved. asn_errorMultipleContacts - 1320 - 1869 + 1106 + 1598 var_errorMessage Assign @@ -74,8 +105,8 @@ ... no Contact was successfully resolved. asn_errorNoContactResolved - 858 - 2535 + 842 + 2006 var_isSuccess Assign @@ -102,8 +133,8 @@ ... update the success flag to denote that resolution was successful; a single contact was resolved. asn_resolutionSuccessful - 1324 - 2296 + 578 + 2114 var_isSuccess Assign @@ -130,8 +161,8 @@ ... capture the count of resolved Contacts driven by the Resolution action. asn_ResolvedContactsCount - 854 - 1655 + 1040 + 1382 var_resolvedContactCount AssignCount @@ -154,8 +185,8 @@ ... assign the resolved Contact to the output variable for processing. asn_resolvedContactToOutputVariable - 1114 - 2036 + 798 + 1706 var_resolvedContact Assign @@ -167,11 +198,28 @@ loop_retrieveResolvedContact + + ... add the Person Account to resolve to the collection being processed. + Create_the_Resolution_Collection_PA + + 231 + 758 + + var_recColPersonAccountsToResolve + Add + + var_personAccountToResolve + + + + ia_resolveB2CCustomerProfilePA + + Error_Contact_Filter - 1305 - 1402 + 314 + 1382 var_errorMessage Add @@ -197,8 +245,8 @@ Error_No_Contact_Resolved - 500 - 1870 + 1370 + 1598 var_isSuccess Assign @@ -217,8 +265,8 @@ Error_Resolution_Exception - 1295 - 906 + 50 + 1166 var_errorMessage Assign @@ -241,12 +289,44 @@ + + Are_PersonAccounts_enabled + + 363 + 650 + + asn_contactToContactCollection + + Standard model + + Person_Accounts_enabled + and + + recGet_defaultConfiguration.Account_Contact_Model__c + EqualTo + + Person + + + + var_personAccountToResolve + IsNull + + false + + + + Create_the_Resolution_Collection_PA + + + + ... evaluate if the resolvedContact has an ID value. dec_doesResolvedContactHaveID - 849 - 2297 + 710 + 1898 asn_errorNoContactResolved @@ -271,10 +351,10 @@ ... evaluate if QA Mode is enabled dec_isQAModeEnabled - 843 - 255 + 363 + 134 - asn_contactToContactCollection + recGet_IdentifyDefaultConfiguration Not Enabled @@ -297,8 +377,8 @@ ... evaluate if a single Contact was resolved dec_wasOneContactResolved - 846 - 1872 + 1040 + 1490 Error_No_Contact_Resolved @@ -338,8 +418,8 @@ ... check if a resolution error was caught (ex. duplicate rules are inactive or unavailable) dec_wasResolutionErrorCaught - 844 - 910 + 363 + 1058 ContactFilterMatches_Subflow @@ -363,8 +443,8 @@ Was_There_An_Error_In_Filtering - 845 - 1405 + 677 + 1274 asn_ResolvedContactsCount @@ -386,14 +466,15 @@ ... encapsulates the Contact Resolution logic for re-use and implementation simplification. + Default B2C Commerce: Process: Contact Resolution Helper {!$Flow.CurrentDateTime} ... iterate over the collection of resolved Contacts to retrieve the specific / resolved Contact Record. loop_retrieveResolvedContact - 856 - 2036 + 710 + 1598 ContactFilterMatches_Subflow.ContactMatchesResult Asc @@ -412,7 +493,7 @@ CanvasMode - FREE_FORM_CANVAS + AUTO_LAYOUT_CANVAS @@ -422,12 +503,48 @@ AutoLaunchedFlow + + ... retrieve the defaultConfiguration for the current b2c-crm-sync instance. + recGet_defaultConfiguration + + 363 + 542 + false + + Are_PersonAccounts_enabled + + and + + Id + EqualTo + + recGet_IdentifyDefaultConfiguration.Active_Configuration__c + + + true + B2C_CRMSync_Setting__mdt + true + + + ... identify which b2c-crm-sync configuration profile is being used as the default. + recGet_IdentifyDefaultConfiguration + + 363 + 434 + false + + recGet_defaultConfiguration + + true + B2C_CRM_Sync_Default_Configuration__mdt + true + ... retrieve the Contact and all of its properties for downstream use. recGet_resolvedContact - 1120 - 2296 + 578 + 2006 false asn_resolutionSuccessful @@ -445,8 +562,8 @@ true - 725 - 30 + 237 + 0 dec_isQAModeEnabled @@ -456,8 +573,8 @@ ... iterate over the collection of returnedResults and pick a winner driven by our resolutionRules. ContactFilterMatches_Subflow - 853 - 1190 + 677 + 1166 Was_There_An_Error_In_Filtering @@ -480,10 +597,10 @@ ... overwrite the contactToResolve value with the QA override. sub_overwriteContactToResolve - 598 - 504 + 231 + 242 - asn_contactToContactCollection + recGet_IdentifyDefaultConfiguration B2CCommerce_Process_QAModeHelper @@ -555,6 +672,14 @@ false + + var_personAccountToResolve + SObject + false + true + false + Account + ... represents the B2C Customer ID used to test the Customer Resolution sub-flow. var_qaB2CCustomerID @@ -632,6 +757,14 @@ false Contact + + var_recColPersonAccountsToResolve + SObject + true + false + false + Account + ... represents the collection of resolvedContacts. var_recColResolvedContacts diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_Process_Contact_HelperServiceEntry.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_Process_Contact_HelperServiceEntry.flow-meta.xml index 83e5dd3b..4ac8ec21 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_Process_Contact_HelperServiceEntry.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_Process_Contact_HelperServiceEntry.flow-meta.xml @@ -5,8 +5,8 @@ ... default the contactToResolve to the sourceContact asn_defaultContactToResolve - 649 - 1062 + 1221 + 566 contactToResolve Assign @@ -22,8 +22,8 @@ ... alert the user that integration is disabled for the specified Contact. asn_errorIntegrationDisabled - 18 - 1280 + 754 + 1190 isSuccess Assign @@ -50,8 +50,8 @@ ... alert the user that integration is disabled for the specified Contact. asn_errorIntegrationDisabledResolvedContact - 2486 - 1276 + 1458 + 1814 isSuccess Assign @@ -78,8 +78,8 @@ ... assign the B2C CustomerList attached to the sourceContact to the IC version. asn_evalB2CCustomerListIC - 1806 - 1278 + 1172 + 1298 icB2CCustomerList Assign @@ -102,8 +102,8 @@ ... expose the multipleResults to the output. asn_exposeMultipleResults - 2443 - 2226 + 1018 + 2138 ResolvedContacts Assign @@ -126,8 +126,8 @@ ... assign a local variable representing the B2C CustomerList and contactToResolve used to validate integration properties. asn_localWorkingVariables - 818 - 1062 + 1089 + 758 b2cCustomerList Assign @@ -143,8 +143,8 @@ ... pass the argument error forward and return it to the caller. asn_outputArgumentError - 1189 - 422 + 50 + 350 ErrorMessage Assign @@ -171,8 +171,8 @@ ... assign the contactResolution output variables asn_outputVariablesNoResolvedContact - 968 - 1565 + 1986 + 1298 Account Assign @@ -234,8 +234,8 @@ ... assign the contactResolution output variables asn_outputVariablesResolutionErrorOccurred - 1583 - 1563 + 1722 + 1298 Account Assign @@ -304,8 +304,8 @@ ... assign the contactResolution output variables asn_outputVariablesSingle - 2226 - 2226 + 1150 + 2330 Account Assign @@ -360,8 +360,8 @@ ... assign the contactResolution output variables asn_outputVariablesSingle_0 - 282 - 2249 + 446 + 1598 Account Assign @@ -416,8 +416,8 @@ ... over-write the contactToResolve as the verifiedContact. asn_overWriteContactToResolve - 1002 - 1062 + 957 + 566 contactToResolve Assign @@ -433,8 +433,8 @@ ... refresh the sourceContact to leverage the generated composite. asn_refreshSourceContact - 817 - 659 + 1089 + 350 sourceContact Assign @@ -450,8 +450,8 @@ ... shorten the contact to include a subset of the overall resolved attributes. asn_shortenContact - 2225 - 1705 + 1150 + 1922 shortenedContact.Id Assign @@ -558,8 +558,8 @@ ... shorten the contact to include a subset of the overall resolved attributes. asn_shortenValidatedContact - 281 - 1609 + 446 + 1190 shortenedContact.Id Assign @@ -666,8 +666,8 @@ ... append the verifiedContact to the resolvedCollection. asn_verifiedContactToResolvedCollection - 27 - 2249 + 314 + 1406 ResolvedContacts Add @@ -683,8 +683,8 @@ ... assign the B2C CustomerList to a local variable dec_assignB2CCustomerListIC - 1801 - 1046 + 1436 + 1406 icB2CCustomerList Assign @@ -707,8 +707,8 @@ ... evaluate if we can allow integration for the specified Contact. dec_allowIntegrationForContact - 272 - 1283 + 600 + 1082 asn_errorIntegrationDisabled @@ -733,8 +733,8 @@ ... evaluate if we can allow integration for the specified Contact. dec_allowIntegrationForResolvedContact - 2216 - 1280 + 1304 + 1706 asn_errorIntegrationDisabledResolvedContact @@ -759,8 +759,8 @@ ... evaluate if an error occurred during the customerResolution process. dec_didResolutionErrorOccur - 1274 - 1567 + 1854 + 1190 asn_outputVariablesNoResolvedContact @@ -792,8 +792,8 @@ ... evaluate if the resolvedContact has a parent B2C CustomerList; if not -- provide one. dec_doesResolvedContactHaveB2CCustomerList - 1531 - 1283 + 1304 + 1190 recGet_ResolvedB2CCustomerList @@ -818,8 +818,8 @@ ... evaluate if an argumentError was caught. dec_hasArgumentError - 808 - 425 + 569 + 242 asn_refreshSourceContact @@ -844,8 +844,8 @@ ... evaluate if all resolutionResults should be included in the output. dec_isReturnMultipleEnabled - 2217 - 1901 + 1150 + 2030 asn_outputVariablesSingle @@ -870,8 +870,8 @@ ... evaluate if all resolutionResults should be included in the output. dec_isReturnMultipleEnabled_0 - 273 - 1892 + 446 + 1298 asn_outputVariablesSingle_0 @@ -896,8 +896,8 @@ ... evaluate if a sourceContact was verified during the validation process. dec_wasCompleteContactVerified - 810 - 1284 + 1089 + 866 sub_contactResolution @@ -943,8 +943,8 @@ ... evaluate if a Contact was resolved via the resolution process. dec_wasContactResolved - 1274 - 1283 + 1579 + 1082 dec_didResolutionErrorOccur @@ -976,8 +976,8 @@ ... evaluate if a sourceContact was verified during the validation process. dec_wasContactVerified - 809 - 846 + 1089 + 458 asn_defaultContactToResolve @@ -1006,6 +1006,7 @@ ... this flow acts as a serviceEntry for the Process and Resolve services -- consuming the same argument structure to provide data exposed by the processServices. + Default B2C Commerce: Process: Contact: Service Helper {!$Flow.CurrentDateTime} @@ -1017,7 +1018,7 @@ CanvasMode - FREE_FORM_CANVAS + AUTO_LAYOUT_CANVAS @@ -1031,8 +1032,8 @@ ... retrieve the Parent Account to the resolvedContact. recGet_ResolvedAccount - 2225 - 1517 + 1150 + 1814 false asn_shortenContact @@ -1058,8 +1059,8 @@ ... retrieve the B2C CustomerList that is the parent to the resolvedContact. recGet_ResolvedB2CCustomerList - 1539 - 1046 + 1436 + 1298 false dec_assignB2CCustomerListIC @@ -1077,8 +1078,8 @@ true - 690 - 50 + 443 + 0 sub_evaluateContactArgument @@ -1088,8 +1089,8 @@ ... evaluate the configuration of the specified CustomerList and sourceContact to determine if integration should be enabled. sub_allowIntegrationHelper - 524 - 1281 + 600 + 974 dec_allowIntegrationForContact @@ -1118,8 +1119,8 @@ ... evaluate the configuration of the specified CustomerList and sourceContact to determine if integration should be enabled. sub_allowResolvedIntegrationHelper - 2016 - 1278 + 1304 + 1598 dec_allowIntegrationForResolvedContact @@ -1148,8 +1149,8 @@ ... attempt to resolve the Contact against the collection of Contacts in this Salesforce Org. sub_contactResolution - 1072 - 1281 + 1579 + 974 dec_wasContactResolved @@ -1160,14 +1161,20 @@ contactToResolve + + var_personAccountToResolve + + sourcePersonAccount + + true ... attempt to evaluate and verify the Contact argument. sub_evaluateContactArgument - 816 - 213 + 569 + 134 dec_hasArgumentError @@ -1380,6 +1387,14 @@ true Contact + + sourcePersonAccount + SObject + false + true + false + Account + ... represents the B2C Commerce customerId representing a customerProfile. This QA variable is used to manually set this contactProperty without defining the contactObject in debug / QA mode. var_qaB2CCustomerID diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_Process_QAModeHelper.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_Process_QAModeHelper.flow-meta.xml index 939447cd..a5afc019 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_Process_QAModeHelper.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_Process_QAModeHelper.flow-meta.xml @@ -137,7 +137,7 @@ var_qaContactID NotEqualTo - + @@ -169,7 +169,7 @@ var_qaB2CCustomerID NotEqualTo - + @@ -201,7 +201,7 @@ var_qaB2CCustomerListID NotEqualTo - + @@ -233,7 +233,7 @@ var_qaB2CCustomerNo NotEqualTo - + @@ -265,7 +265,7 @@ var_qaEmailAddress NotEqualTo - + @@ -297,7 +297,7 @@ var_qaFirstName NotEqualTo - + @@ -329,7 +329,7 @@ var_qaLastName NotEqualTo - + diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CCustomerList_SitesActivate.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CCustomerList_SitesActivate.flow-meta.xml index 4c475f82..3d4d30ca 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CCustomerList_SitesActivate.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CCustomerList_SitesActivate.flow-meta.xml @@ -126,19 +126,19 @@ - Enable_Order_on_Behalf_Of__c + Enable_Active_Customer_Promotion_Sync__c true - Enable_Password_Reset__c + Enable_Order_on_Behalf_Of__c true - Enable_Active_Customer_Promotion_Sync__c + Enable_Password_Reset__c true diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CCustomerList_SitesDeActivate.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CCustomerList_SitesDeActivate.flow-meta.xml index 0dd94aab..a95b2acb 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CCustomerList_SitesDeActivate.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CCustomerList_SitesDeActivate.flow-meta.xml @@ -126,19 +126,19 @@ - Enable_Order_on_Behalf_Of__c + Enable_Active_Customer_Promotion_Sync__c false - Enable_Password_Reset__c + Enable_Order_on_Behalf_Of__c false - Enable_Active_Customer_Promotion_Sync__c + Enable_Password_Reset__c false diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CInstance_SitesActivate.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CInstance_SitesActivate.flow-meta.xml index 34414e76..8a647de6 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CInstance_SitesActivate.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CInstance_SitesActivate.flow-meta.xml @@ -126,19 +126,19 @@ - Enable_Order_on_Behalf_Of__c + Enable_Active_Customer_Promotion_Sync__c true - Enable_Password_Reset__c + Enable_Order_on_Behalf_Of__c true - Enable_Active_Customer_Promotion_Sync__c + Enable_Password_Reset__c true diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CInstance_SitesDeActivate.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CInstance_SitesDeActivate.flow-meta.xml index 0280362b..7727129c 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CInstance_SitesDeActivate.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_B2CInstance_SitesDeActivate.flow-meta.xml @@ -124,19 +124,19 @@ - Enable_Order_on_Behalf_Of__c + Enable_Active_Customer_Promotion_Sync__c false - Enable_Password_Reset__c + Enable_Order_on_Behalf_Of__c false - Enable_Active_Customer_Promotion_Sync__c + Enable_Password_Reset__c false diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_Contact_OrderOnBehalfOf.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_Contact_OrderOnBehalfOf.flow-meta.xml index e109e997..b4c4327b 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_Contact_OrderOnBehalfOf.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_Contact_OrderOnBehalfOf.flow-meta.xml @@ -770,7 +770,6 @@ Screen_Error_Auth_Section1 RegionContainer - SectionWithoutHeader Screen_Error_Auth_Section1_Column1 Region @@ -819,11 +818,11 @@ false false + SectionWithoutHeader Screen_Error_Auth_Section2 RegionContainer - SectionWithoutHeader Screen_Error_Auth_Section2_Column1 Region @@ -895,6 +894,7 @@ false false + SectionWithoutHeader lbl_OOBORESTAPIError @@ -924,7 +924,6 @@ Screen_Init_OOBO_Section1 RegionContainer - SectionWithoutHeader Screen_Init_OOBO_Section1_Column1 Region @@ -973,6 +972,7 @@ false false + SectionWithoutHeader lbl_introTextFooter @@ -1002,7 +1002,6 @@ scrn_b2cSiteSelect_Section1 RegionContainer - SectionWithoutHeader scrn_b2cSiteSelect_Section1_Column1 Region @@ -1064,6 +1063,7 @@ false false + SectionWithoutHeader true false @@ -1085,7 +1085,6 @@ scrn_launchOOBOShoppingSession_Section1 RegionContainer - SectionWithoutHeader scrn_launchOOBOShoppingSession_Section1_Column1 Region @@ -1203,6 +1202,7 @@ false false + SectionWithoutHeader lbl_OOBOSuccessCustomerDetails @@ -1212,7 +1212,6 @@ scrn_launchOOBOShoppingSession_Section2 RegionContainer - SectionWithoutHeader scrn_launchOOBOShoppingSession_Section2_Column1 Region @@ -1306,6 +1305,7 @@ false false + SectionWithoutHeader lbl_OOBOSuccessStorefrontDetails @@ -1315,7 +1315,6 @@ scrn_launchOOBOShoppingSession_Section3 RegionContainer - SectionWithoutHeader scrn_launchOOBOShoppingSession_Section3_Column1 Region @@ -1439,6 +1438,7 @@ false false + SectionWithoutHeader <p>Click <b>Finish </b>to <b>cancel </b>OOBO launch.</p> true @@ -1461,7 +1461,6 @@ scrn_noActiveB2CSitesFound_Section1 RegionContainer - SectionWithoutHeader scrn_noActiveB2CSitesFound_Section1_Column1 Region @@ -1516,6 +1515,7 @@ false false + SectionWithoutHeader true false @@ -1542,7 +1542,6 @@ scrn_noAnonCustomerIDSiteConfig_Section1 RegionContainer - SectionWithoutHeader scrn_noAnonCustomerIDSiteConfig_Section1_Column1 Region @@ -1646,6 +1645,7 @@ false false + SectionWithoutHeader lbl_CustomerNoIssueFooter @@ -1677,7 +1677,6 @@ scrn_OOBOConfigurationInvalid_Section1 RegionContainer - SectionWithoutHeader scrn_OOBOConfigurationInvalid_Section1_Column1 Region @@ -1809,6 +1808,7 @@ false false + SectionWithoutHeader lbl_b2cSiteConfigurationErrorFooter diff --git a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_Contact_PasswordReset.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_Contact_PasswordReset.flow-meta.xml index bdea3553..2f3495d4 100644 --- a/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_Contact_PasswordReset.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CCommerce_QuickAction_Contact_PasswordReset.flow-meta.xml @@ -693,7 +693,6 @@ Screen_Error_Auth_Section1 RegionContainer - SectionWithoutHeader Screen_Error_Auth_Section1_Column1 Region @@ -742,11 +741,11 @@ false false + SectionWithoutHeader Screen_Error_Auth_Section2 RegionContainer - SectionWithoutHeader Screen_Error_Auth_Section2_Column1 Region @@ -818,6 +817,7 @@ false false + SectionWithoutHeader lbl_RESTAPIError @@ -847,7 +847,6 @@ Screen_Init_PasswordReset_Section1 RegionContainer - SectionWithoutHeader Screen_Init_PasswordReset_Section1_Column1 Region @@ -896,6 +895,7 @@ false false + SectionWithoutHeader lbl_introTextFooter @@ -925,7 +925,6 @@ scrn_b2cSiteSelect_Section1 RegionContainer - SectionWithoutHeader scrn_b2cSiteSelect_Section1_Column1 Region @@ -987,6 +986,7 @@ false false + SectionWithoutHeader true false @@ -1008,7 +1008,6 @@ scrn_noActiveB2CSitesFound_Section1 RegionContainer - SectionWithoutHeader scrn_noActiveB2CSitesFound_Section1_Column1 Region @@ -1063,6 +1062,7 @@ false false + SectionWithoutHeader true false @@ -1084,7 +1084,6 @@ scrn_Password_Reset_Session_Success_Section1 RegionContainer - SectionWithoutHeader scrn_Password_Reset_Session_Success_Section1_Column1 Region @@ -1133,6 +1132,7 @@ false false + SectionWithoutHeader lbl_successCustomerDetails @@ -1142,7 +1142,6 @@ scrn_Password_Reset_Session_Success_Section2 RegionContainer - SectionWithoutHeader scrn_Password_Reset_Session_Success_Section2_Column1 Region @@ -1236,6 +1235,7 @@ false false + SectionWithoutHeader <p>Click <b>Finish </b>to <b>cancel </b>OOBO launch.</p> true @@ -1263,7 +1263,6 @@ scrn_PasswordResetConfigurationInvalid_Section1 RegionContainer - SectionWithoutHeader scrn_PasswordResetConfigurationInvalid_Section1_Column1 Region @@ -1368,6 +1367,7 @@ false false + SectionWithoutHeader lbl_b2cSiteConfigurationErrorFooter diff --git a/src/sfdc/base/main/default/flows/B2CContactProcess.flow-meta.xml b/src/sfdc/base/main/default/flows/B2CContactProcess.flow-meta.xml index 2ae44e87..ccd63613 100644 --- a/src/sfdc/base/main/default/flows/B2CContactProcess.flow-meta.xml +++ b/src/sfdc/base/main/default/flows/B2CContactProcess.flow-meta.xml @@ -1,11 +1,37 @@ + + Evaluate_source_input + + 1172 + 134 + B2CContactProcessEvaluateSourceInput + apex + + sub_resolveCustomerProfile + + CurrentTransaction + + jsonRepresentation + + sourceInput.jsonRepresentation + + + + sourceContact + sourceContact + + + sourcePersonAccount + sourcePersonAccount + + ... this action remove the read-only PersonAccount properties attached to a Contact from the Contact -- so that the Contact record can be independently updated. ia_removeROAccountPropertiesFromContact 578 - 1454 + 1406 B2CIARemovePAPropertiesFromContact apex @@ -28,7 +54,7 @@ ia_synchronizeContactValues 600 - 638 + 674 B2CIASynchronizeContact apex @@ -57,7 +83,7 @@ asn_AccountIDToSourceContact 578 - 2030 + 1922 resolvedContactToUpdate.AccountId Assign @@ -73,8 +99,8 @@ ... assign the Account Name to the Business Account after the recordType is created. asn_AccountNameToBusinessAccount - 1458 - 1574 + 1722 + 1622 Account.Name Assign @@ -90,8 +116,8 @@ ... assign the B2C Account Properties to the Contact's parent Account (Name and RecordTypeId). asn_B2CAccountRecordType - 1458 - 1334 + 1722 + 1406 Account.RecordTypeId Assign @@ -122,7 +148,7 @@ asn_B2CCustomerListRelationship_Update 798 - 1118 + 1106 resolvedContactToUpdate.B2C_CustomerList_ID__c Assign @@ -174,7 +200,7 @@ asn_contactProcessSuccessfully_Update 710 - 2246 + 2114 Contact Assign @@ -202,7 +228,7 @@ asn_ErrorContactIntegrationDisabled_Assignment 314 - 878 + 890 errors Add @@ -229,8 +255,8 @@ ... throw an error explaining the Contact's attributes must include a LastName if a CustomerList / CustomerNo are not provided. asn_ErrorContactLastNameMissing_Assignment - 1326 - 998 + 1590 + 1106 synchronizedContact.LastName Assign @@ -246,8 +272,8 @@ ... throw an error explaining that multiple Contacts were resolved. asn_ErrorMultipleContactsResolved_Assignment - 1766 - 638 + 2030 + 674 isSuccess Assign @@ -267,8 +293,8 @@ ... assign the parent B2C Commerce Account to the Contact asn_parentB2CAccount - 1458 - 1814 + 1722 + 1838 synchronizedContact.AccountId Assign @@ -284,8 +310,8 @@ ... assign the parent B2C CustomerList to the sourceContact. asn_parentB2CCustomerListID - 1458 - 758 + 1722 + 890 synchronizedContact.B2C_CustomerList__c Assign @@ -309,7 +335,7 @@ asn_resolutionError 50 - 638 + 674 isSuccess Assign @@ -325,12 +351,36 @@ + + ... assign the parent B2C CustomerList to the sourceParentAccount. + Assign_the_B2C_CustomerList_PA + + 1326 + 890 + + sourcePersonAccount.B2C_CustomerList__pc + Assign + + var_b2cCustomerList.Id + + + + sourcePersonAccount.B2C_CustomerList_ID__pc + Assign + + var_b2cCustomerList.Name + + + + Create_the_Person_Account + + ... set the success flag and announce that the Contact Processing was successful ContactCreateSuccess_Assignment - 1458 - 2630 + 1524 + 2246 isSuccess Assign @@ -346,12 +396,51 @@ + + Set_Audit_on_Person_Account + + 1326 + 782 + + sourcePersonAccount.Audit_OCAPI_API_Response__pc + Assign + + false + + + + Assign_the_B2C_CustomerList_PA + + + + Set_synchronized_contact + + 1326 + 1214 + + synchronizedContact + Assign + + Retrieve_contact_tied_to_the_Person_Account + + + + Account + Assign + + sourcePersonAccount + + + + B2CCommerce_Process_PlatformEventHelper + + ... evaluate if we can resolve the Contact record using the identifiers provided. dec_CanWeResolveThisContact - 1040 - 518 + 1172 + 566 Unknown Outcome decOut_resolutionError @@ -415,7 +504,7 @@ - sub_B2CCommerceContactAssignmentHelper + Retrieve_the_Customer_Model_1 @@ -472,8 +561,8 @@ ... evaluate if a Contact has a lastName assigned -- and prevent saving of the Account / Contact records if one is not provided. dec_doesContactHaveLastName - 1458 - 878 + 1722 + 998 recGet_B2CRecordType @@ -499,7 +588,7 @@ dec_EvaluateContactRecord 600 - 758 + 782 dec_isTrustworthyIDPresentInOriginalContact @@ -525,7 +614,7 @@ dec_isContactMissingAccountID 710 - 1910 + 1814 asn_contactProcessSuccessfully_Update @@ -551,7 +640,7 @@ dec_isTrustworthyIDPresentInOriginalContact 886 - 878 + 890 asn_cannotUpdateWithoutIdentifiers @@ -573,17 +662,17 @@ - ... retrieve the configured customerModel to leverage when synchronizing B2C Commerce Customer Profiles. - dec_RetrieveCustomerModel - - 1458 - 2054 + ... evaluate if PersonAccounts are enabled as the active customerModel. + dec_updateArePersonAccountsEnabled + + 710 + 1298 - B2CCommerce_Process_PlatformEventHelper + recUpd_sourceContactUpdate - Accounts / Contacts Enabled + Not Enabled - decOut_PersonAccountsEnabled + decOut_updatePersonAccountsEnabledTrue and recGet_defaultConfiguration.Account_Contact_Model__c @@ -593,23 +682,23 @@ - recGet_B2CPARecordType + ia_removeROAccountPropertiesFromContact - + - ... evaluate if PersonAccounts are enabled as the active customerModel. - dec_updateArePersonAccountsEnabled - - 710 - 1334 + ... retrieve the configured customerModel to leverage when synchronizing B2C Commerce Customer Profiles. + Retrieve_the_Customer_Model_1 + + 1524 + 674 - recUpd_sourceContactUpdate + sub_B2CCommerceContactAssignmentHelper - Not Enabled + Accounts / Contacts Enabled - decOut_updatePersonAccountsEnabledTrue + PersonAccounts_Enabled_1 and recGet_defaultConfiguration.Account_Contact_Model__c @@ -619,12 +708,13 @@ - ia_removeROAccountPropertiesFromContact + Set_Audit_on_Person_Account - + ... this flow is used to execute Contact Processing requests from B2C Commerce -- and evaluate if a Contact record should be created or updated within the Salesforce Platform. + Default B2C Commerce: Create Or Update Contact {!$Flow.CurrentDateTime} @@ -646,14 +736,25 @@ AutoLaunchedFlow + + ... create the Person Account + Create_the_Person_Account + + 1326 + 998 + + Retrieve_contact_tied_to_the_Person_Account + + sourcePersonAccount + ... create the Contact Record for the B2C Business Account recCreate_BusinessRecord_Create - 1458 - 1934 + 1722 + 1946 - dec_RetrieveCustomerModel + B2CCommerce_Process_PlatformEventHelper synchronizedContact @@ -661,41 +762,19 @@ ... create the Parent B2C Account so that we can attach the Contact record to it. recCreate_ParentB2CAccount - 1458 - 1694 + 1722 + 1730 asn_parentB2CAccount Account - - ... retrieve the recordType describing B2C Customer Profiles so it can be added to the Person Account. - recGet_B2CPARecordType - - 1326 - 2174 - false - - recUpd_PersonAccountRecordType - - and - - DeveloperName - EqualTo - - recGet_defaultConfiguration.PersonAccount_Record_Type_Developername__c - - - true - RecordType - true - ... retrieve the recordType describing B2C Customer Profiles so it can be added to the Account. recGet_B2CRecordType - 1458 - 1214 + 1722 + 1298 false asn_B2CAccountRecordType @@ -716,8 +795,8 @@ ... retrieve the defaultConfiguration for the current b2c-crm-sync instance. recGet_defaultConfiguration - 1040 - 398 + 1172 + 458 false dec_CanWeResolveThisContact @@ -738,8 +817,8 @@ ... identify which b2c-crm-sync configuration profile is being used as the default. recGet_IdentifyDefaultConfiguration - 1040 - 278 + 1172 + 350 false recGet_defaultConfiguration @@ -748,47 +827,43 @@ B2C_CRM_Sync_Default_Configuration__mdt true - - ... update the recordType on the Account to convert it to a PersonAccount. - recUpd_PersonAccountRecordType - + + Retrieve_contact_tied_to_the_Person_Account + 1326 - 2294 + 1106 + false - B2CCommerce_Process_PlatformEventHelper + Set_synchronized_contact and - Id + AccountId EqualTo - Account.Id + sourcePersonAccount.Id - - RecordTypeId - - recGet_B2CPARecordType.Id - - - Account - + true + Contact + true + ... update the sourceContact record accordingly. recUpd_sourceContactUpdate 710 - 1670 + 1598 B2CCommerce_PlatformEvent_ProcessContactUpdate resolvedContactToUpdate - 914 + 1046 0 - sub_resolveCustomerProfile + Evaluate_source_input Active @@ -797,7 +872,7 @@ B2CCommerce_PlatformEvent_ProcessContactUpdate 710 - 1790 + 1706 dec_isContactMissingAccountID @@ -826,8 +901,8 @@ ... generate the Contact Process Platform Event. B2CCommerce_Process_PlatformEventHelper - 1458 - 2510 + 1524 + 2138 ContactCreateSuccess_Assignment @@ -856,8 +931,8 @@ ... this flow is used to take Contact properties from the sourceContact presented to this flow -- and assign them to the output Contact that will be returned in the flow results. sub_B2CCommerceContactAssignmentHelper - 1458 - 638 + 1722 + 782 asn_parentB2CCustomerListID @@ -889,8 +964,8 @@ ... attempt to calculate the Account Name based on the Contact properties provided. sub_calculateAccountName - 1458 - 1454 + 1722 + 1514 asn_AccountNameToBusinessAccount @@ -916,8 +991,8 @@ ... attempt to resolve a customerProfile using the serviceArguments. sub_resolveCustomerProfile - 1040 - 158 + 1172 + 242 recGet_IdentifyDefaultConfiguration @@ -934,6 +1009,12 @@ sourceContact + + sourcePersonAccount + + sourcePersonAccount + + var_qaB2CCustomerID @@ -1121,6 +1202,22 @@ false Contact + + sourceInput + B2CContactProcessSourceInput + Apex + false + true + false + + + sourcePersonAccount + SObject + false + false + false + Account + ... represents the combination of the source and resolvedContact. synchronizedContact diff --git a/src/sfdc/base/main/default/objects/B2C_Integration_Field_Mappings__mdt/listViews/B2C_Customer_Profile_Account_Mappings.listView-meta.xml b/src/sfdc/base/main/default/objects/B2C_Integration_Field_Mappings__mdt/listViews/B2C_Customer_Profile_Account_Mappings.listView-meta.xml new file mode 100644 index 00000000..2b4f35c5 --- /dev/null +++ b/src/sfdc/base/main/default/objects/B2C_Integration_Field_Mappings__mdt/listViews/B2C_Customer_Profile_Account_Mappings.listView-meta.xml @@ -0,0 +1,17 @@ + + + B2C_Customer_Profile_Account_Mappings + MasterLabel + DeveloperName + B2C_Commerce_OCAPI_Attribute__c + Service_Cloud_Attribute_Alt__c + Service_Cloud_Attribute__c + Enable_for_Integration__c + Everything + + Service_Cloud_Object__c + equals + Account + + + diff --git a/src/sfdc/extras/sf-connect/base/main/default/classes/B2CDataSourceAddressBookConnection.cls b/src/sfdc/extras/sf-connect/base/main/default/classes/B2CDataSourceAddressBookConnection.cls index d7ec6976..62d9d5d6 100644 --- a/src/sfdc/extras/sf-connect/base/main/default/classes/B2CDataSourceAddressBookConnection.cls +++ b/src/sfdc/extras/sf-connect/base/main/default/classes/B2CDataSourceAddressBookConnection.cls @@ -29,8 +29,8 @@ global with sharing class B2CDataSourceAddressBookConnection extends DataSource. * create the datasourceConnection for B2C Commerce addressBooks */ global B2CDataSourceAddressBookConnection(DataSource.ConnectionParams connectionParams) { - this.retrievalFieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval(this.tableName + '__x'); - this.publishingFieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing(this.tableName + '__x'); + this.retrievalFieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval(this.tableName + '__x', false); + this.publishingFieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing(this.tableName + '__x', false); this.indirectLookupTargetObject = B2CConfigurationManager.getDefaultAccountContactModel() == B2CConstant.ACCOUNTCONTACTMODEL_PERSON ? 'Account' : 'Contact'; } diff --git a/src/sfdc/personaccounts/main/default/classes/B2CAccountManager.cls b/src/sfdc/personaccounts/main/default/classes/B2CAccountManager.cls index 6875fdf1..b4d5ecc3 100644 --- a/src/sfdc/personaccounts/main/default/classes/B2CAccountManager.cls +++ b/src/sfdc/personaccounts/main/default/classes/B2CAccountManager.cls @@ -36,7 +36,7 @@ public with sharing class B2CAccountManager extends B2CBaseMeta { // Loop over the field-mappings and attribute the updated / changed values for (B2C_Integration_Field_Mappings__mdt thisFieldMapping : updatedFieldMappings) { - if (thisFieldMapping.Service_Cloud_Attribute_Alt__c != null){ + if (thisFieldMapping.Service_Cloud_Attribute_Alt__c != null && B2CBaseAttributeAssignment.doesFieldExist(B2CBaseAttributeAssignment.getSchemaMap(output), thisFieldMapping.Service_Cloud_Attribute__c)) { // Seed the contact with each modified property of the Account output.put( thisFieldMapping.Service_Cloud_Attribute__c, diff --git a/src/sfdc/personaccounts/main/default/classes/B2CIACustomerResolutionPA_Test.cls b/src/sfdc/personaccounts/main/default/classes/B2CIACustomerResolutionPA_Test.cls index 976fa010..8e70dcd7 100644 --- a/src/sfdc/personaccounts/main/default/classes/B2CIACustomerResolutionPA_Test.cls +++ b/src/sfdc/personaccounts/main/default/classes/B2CIACustomerResolutionPA_Test.cls @@ -35,6 +35,133 @@ public class B2CIACustomerResolutionPA_Test { } + @IsTest + private static void testResolveFailure() { + + // Initialize local variables + List resolutionResults; + List> testAccounts; + + Test.startTest(); + + // Initialize the testContacts collection + testAccounts = new List>(); + + // Attempt to resolve the empty resultSet + resolutionResults = B2CIACustomerResolutionPA.resolve(testAccounts); + + Test.stopTest(); + + // Iterate over the collection of resolutionResults + for (B2CIACustomerResolutionResult thisResult: resolutionResults) { + + // Validate than an error was thrown by the resolveMethod + System.assert(thisResult.isError, 'Expected an error to be thrown as no sourceContacts were passed to the resolve() method'); + + } + + } + + + + @IsTest + private static void testResolveFailureDueToMissingAccounts() { + + // Initialize local variables + List resolutionResults; + List> testAccounts; + + Test.startTest(); + + // Initialize the testContacts collection + testAccounts = new List>(); + testAccounts.add(new List()); + + // Attempt to resolve the empty resultSet + resolutionResults = B2CIACustomerResolutionPA.resolve(testAccounts); + + Test.stopTest(); + + // Iterate over the collection of resolutionResults + for (B2CIACustomerResolutionResult thisResult: resolutionResults) { + + // Validate than an error was thrown by the resolveMethod + System.assert(thisResult.isError, 'Expected an error to be thrown as no sourceContacts were passed to the resolve() method'); + + } + + } + + /** + * @see B2CIACustomerResolution.resolve + * @description + * Given: Existing person account with a last name, email, customer id + * When: We resolve with a customer with a different last name, email, but same customer id + * Then: We will be returned the existing person account + **/ + @IsTest + static void resolvesCustomerIdFailureWithPA() { + // Create the test / parent Account + Account account = (Account)TestDataFactory.createSObject('Account', new Map{ + 'PersonEmail' => 'differntemail@email.com', + 'LastName' => 'DifferentLastName', + 'B2C_Customer_ID__pc' => 'customerid', + 'RecordTypeId' => B2CIACustomerResolution_TestHelper.getRecordType(B2CConfigurationManager.getPersonAccountRecordTypeDeveloperName()).Id + }); + + // Create the childContact + Account a1 = new Account( + PersonEmail = 'email@email.com', + LastName = 'LastName', + B2C_Customer_ID__pc = 'customerid', + RecordTypeId = B2CIACustomerResolution_TestHelper.getRecordType(B2CConfigurationManager.getPersonAccountRecordTypeDeveloperName()).Id + ); + + // Create the local working variables + List contactResolvedList = new List(); + List contactFilteredList = new List(); + List resolutionAccountList = new List(); + List> resolutionArguments = new List>(); + + Test.startTest(); + + // Initialize the resolution arguments + resolutionAccountList.add(a1); + resolutionArguments.add(resolutionAccountList); + + // Create the list of resolved contacts + contactResolvedList = B2CIACustomerResolutionPA.resolve(resolutionArguments); + + // Default the working collections leveraged by the resolution / filter process + B2CIACustomerResolutionResult resolutionResults = contactResolvedList.get(0); + Map contactFilterMap = new Map(); + + // Add the matches to the flowArguments + contactFilterMap.put( + 'ContactMatches', + resolutionResults.contactList + ); + + // Create the flow interview to process the arguments + Flow.Interview.B2CCommerce_Process_ContactFilterMatches contactFilterMatchFlow = + new Flow.Interview.B2CCommerce_Process_ContactFilterMatches(contactFilterMap); + + // Start the flow + contactFilterMatchFlow.start(); + + // Retrieve the list of filtered contacts + contactFilteredList = (List)(contactFilterMatchFlow.getVariableValue('ContactMatchesResult')); + + Test.stopTest(); + + // Validate that our contact was matched-up + System.assertEquals( + contactFilteredList, + null + ); + + } + /** * @see B2CIACustomerResolution.resolve * @description diff --git a/src/sfdc/personaccounts/main/default/classes/B2CIAProcessCustomerProfilePA_Test.cls b/src/sfdc/personaccounts/main/default/classes/B2CIAProcessCustomerProfilePA_Test.cls new file mode 100644 index 00000000..3ba0b3c5 --- /dev/null +++ b/src/sfdc/personaccounts/main/default/classes/B2CIAProcessCustomerProfilePA_Test.cls @@ -0,0 +1,58 @@ +/** + * @author Eric Schultz + * @date April 16, 2020 + * + * @description This class is used to exercise the processCustomerProfile class and + * exercise the logic used to interact / synchronize with B2C Commerce customerProfiles. + */ +@IsTest +private class B2CIAProcessCustomerProfilePA_Test { + + @IsTest + static void testProcessCustomerProfilePersonAccountUpdateSuccess() { + + // Initialize local variables + List requestArguments = new List(); + B2CIAGetCustomerProfileResult input = new B2CIAGetCustomerProfileResult(); + + List accountFieldMappings = B2CMetaFieldMappings.getFieldMappingsForRetrieval('Account', true); + if (accountFieldMappings.size() == 0) { + Assert.isTrue(true, 'No Account based fields mapping, abort this test.'); + return; + } + + // Create a test account that we'll exercise + Account a = (Account)TestDataFactory.createSObject('Account', new Map{ + 'FirstName' => 'firstName', + 'LastName' => 'originalLastName', + 'PersonEmail' => 'test-user@b2csa.qa.salesforce.com', + accountFieldMappings.get(0).Service_Cloud_Attribute__c => 'OriginalValue', + 'RecordTypeId' => B2CIACustomerResolution_TestHelper.getRecordType(B2CConfigurationManager.getPersonAccountRecordTypeDeveloperName()).Id + }); + + Contact c = [SELECT Id, AccountId FROM Contact WHERE AccountId = :a.Id]; + + // Seed the request arguments + input.crmContactId = c.Id; + input.responseBody = '{"' + accountFieldMappings.get(0).B2C_Commerce_OCAPI_Attribute__c + '": "updatedValue"}'; + requestArguments.add(input); + + Test.startTest(); + + // Attempt to update the customerProfiles using the properties / specified + B2CIAProcessCustomerProfile.updateCustomerProfiles(requestArguments); + + // Retrieve the updatedAccount using the same fields + String query = 'SELECT Id, ' + accountFieldMappings.get(0).Service_Cloud_Attribute__c + ' FROM Account WHERE Id = :accountId LIMIT 1'; + Account updatedAccount = Database.queryWithBinds(query, new Map { + 'accountId' => a.Id + }, AccessLevel.SYSTEM_MODE); + + Test.stopTest(); + + // Validate that the specific customerProfile updates were processed successfully + System.assertEquals(updatedAccount.get(accountFieldMappings.get(0).Service_Cloud_Attribute__c), 'updatedValue', 'Expected the site for the account to be updated with the responseBody contents'); + + } + +} diff --git a/src/sfdc/personaccounts/main/default/classes/B2CIAProcessCustomerProfilePA_Test.cls-meta.xml b/src/sfdc/personaccounts/main/default/classes/B2CIAProcessCustomerProfilePA_Test.cls-meta.xml new file mode 100644 index 00000000..40d67933 --- /dev/null +++ b/src/sfdc/personaccounts/main/default/classes/B2CIAProcessCustomerProfilePA_Test.cls-meta.xml @@ -0,0 +1,5 @@ + + + 54.0 + Active + diff --git a/src/sfdc/personaccounts/main/default/classes/B2CProcessPersonAccountHelper.cls b/src/sfdc/personaccounts/main/default/classes/B2CProcessPersonAccountHelper.cls index 47b9cba3..fb68b267 100644 --- a/src/sfdc/personaccounts/main/default/classes/B2CProcessPersonAccountHelper.cls +++ b/src/sfdc/personaccounts/main/default/classes/B2CProcessPersonAccountHelper.cls @@ -22,25 +22,23 @@ public with sharing class B2CProcessPersonAccountHelper { Account newPersonAccount; Account oldPersonAccount; Contact validateContact; - Contact publishContact; Map instanceMap; Map customerListMap; B2CIAValidateContactInput validateContactInput; B2CIAValidateContactResult validateContactResult; - List fieldMappings; - List contactFieldMappings; - List updatedFieldMappings; - List publishFieldMappings; + List contactFieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing('Contact', false); + List accountFieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing('Account', true); + List updatedFieldMappingsContact; + List updatedFieldMappingsAccount; Map thisB2CProfile; String thisB2CProfileJSON; List peCollection; // Get the fieldMappings for the customerProfile object - contactFieldMappings = B2CMetaFieldMappings.getFieldMappingsForPublishing('Contact'); - fieldMappings = B2CMetaFieldMappings.toggleAlternateObjectAttributes(contactFieldMappings); + contactFieldMappings = B2CMetaFieldMappings.toggleAlternateObjectAttributes(contactFieldMappings); // Only process the trigger if publish-friendly fieldMappings are found - if (fieldMappings.size() > 0) { + if (contactFieldMappings.size() > 0 || accountFieldMappings.size() > 0) { // Initialize the instance maps instanceMap = new Map(); @@ -103,19 +101,24 @@ public with sharing class B2CProcessPersonAccountHelper { if (validateContactResult.allowIntegrationProcess == false) { continue; } // Determine if this Contact been updated through one of the publish fieldMappings - updatedFieldMappings = B2CProcessContactHelper.getUpdatedFieldMappings(oldPersonAccount, newPersonAccount, fieldMappings); - - // Has the Contact record been updated? - if (updatedFieldMappings.size() > 0) { - - // Toggle the fieldMappings back to the Contact so we can use them for the publishEvent - publishFieldMappings = B2CMetaFieldMappings.toggleAlternateObjectAttributes(updatedFieldMappings); - - // Generate a contact-representation of the Account that only includes publishable fields - publishContact = B2CAccountManager.getPublishContact(newPersonAccount, updatedFieldMappings); - - // If so, get the field-specific updates for the updated contact - thisB2CProfile = B2CContactManager.getPublishProfile(publishContact, publishFieldMappings); + updatedFieldMappingsContact = B2CProcessContactHelper.getUpdatedFieldMappings(oldPersonAccount, newPersonAccount, contactFieldMappings); + updatedFieldMappingsAccount = B2CProcessContactHelper.getUpdatedFieldMappings(oldPersonAccount, newPersonAccount, accountFieldMappings); + + System.debug(LoggingLevel.DEBUG, '--> Updated field mappings for contact (' + updatedFieldMappingsContact.size() + ')'); + System.debug(LoggingLevel.DEBUG, '--> Updated field mappings for account (' + updatedFieldMappingsAccount.size() + ')'); + + // Has the PersonAccount record been updated? + if (updatedFieldMappingsContact.size() > 0 || updatedFieldMappingsAccount.size() > 0) { + + // If so, get the field-specific updates for the updated person account + thisB2CProfile = B2CContactAccountManager.getPublishProfile( + newPersonAccount, + updatedFieldMappingsAccount, + B2CContactManager.getPublishProfile( + B2CAccountManager.getPublishContact(newPersonAccount, updatedFieldMappingsContact), + B2CMetaFieldMappings.toggleAlternateObjectAttributes(updatedFieldMappingsContact) + ) + ); thisB2CProfileJSON = JSON.serializePretty(thisB2CProfile, true); // Then create the Contact Publish Platform Event and override the publish JSON diff --git a/src/sfdc/personaccounts/main/default/flows/B2CCommerce_QuickAction_PersonAccount_OrderOnBehalfOf.flow-meta.xml b/src/sfdc/personaccounts/main/default/flows/B2CCommerce_QuickAction_PersonAccount_OrderOnBehalfOf.flow-meta.xml index 709c648b..7cd02aa8 100644 --- a/src/sfdc/personaccounts/main/default/flows/B2CCommerce_QuickAction_PersonAccount_OrderOnBehalfOf.flow-meta.xml +++ b/src/sfdc/personaccounts/main/default/flows/B2CCommerce_QuickAction_PersonAccount_OrderOnBehalfOf.flow-meta.xml @@ -95,7 +95,6 @@ scrn_noContactFoundError_Section1 RegionContainer - SectionWithoutHeader scrn_noContactFoundError_Section1_Column1 Region @@ -158,6 +157,7 @@ false false + SectionWithoutHeader true true diff --git a/src/sfdc/personaccounts/main/default/flows/B2CCommerce_QuickAction_PersonAccount_PasswordReset.flow-meta.xml b/src/sfdc/personaccounts/main/default/flows/B2CCommerce_QuickAction_PersonAccount_PasswordReset.flow-meta.xml index b991120d..f6d909e8 100644 --- a/src/sfdc/personaccounts/main/default/flows/B2CCommerce_QuickAction_PersonAccount_PasswordReset.flow-meta.xml +++ b/src/sfdc/personaccounts/main/default/flows/B2CCommerce_QuickAction_PersonAccount_PasswordReset.flow-meta.xml @@ -95,7 +95,6 @@ scrn_noContactFoundError_Section1 RegionContainer - SectionWithoutHeader scrn_noContactFoundError_Section1_Column1 Region @@ -159,6 +158,7 @@ false false + SectionWithoutHeader true true diff --git a/src/sfdc/personaccounts/main/default/triggers/B2CProcessPersonAccount.trigger b/src/sfdc/personaccounts/main/default/triggers/B2CProcessPersonAccount.trigger index 8e959c6b..4e1163e4 100644 --- a/src/sfdc/personaccounts/main/default/triggers/B2CProcessPersonAccount.trigger +++ b/src/sfdc/personaccounts/main/default/triggers/B2CProcessPersonAccount.trigger @@ -19,6 +19,9 @@ trigger B2CProcessPersonAccount on Account (before update) { } - } catch (Exception e) {} + } catch (Exception e) { + // Audit that an error was caught + System.debug(System.LoggingLevel.ERROR, '--> B2C Exception: ' + e.getMessage()); + } }