Loading
Set Up and Maintain Retail Execution
Table of Contents
Select Filters

          No results
          No results
          Here are some search tips

          Check the spelling of your keywords.
          Use more general search terms.
          Select fewer filters to broaden your search.

          Search all of Salesforce Help
          Recommend Visits Using Apex

          Recommend Visits Using Apex

          Create an Apex class to list of specific stores that Einstein must provide visit recommendations for. You can then create a flow and add the Apex Class as an Action element.

          Required Editions

          Available in Lightning Experience in Professional, Unlimited, and Enterprise Editions that have Consumer Goods Cloud enabled.
          Important
          Important To use an Apex action in a flow, ask your developer to annotate the appropriate method with @InvocableMethod.

          Here's what an Apex class for filtering stores looks like:

          global class BulkGetVisitRecommendations {
            @InvocableMethod(label='Bulk Get Visit Recommendations from Apex')
            global static List<Recommendation> getRecommendations(List<List<String>> inputList) {
               List<Recommendation> recommendations = new List<Recommendation>();
               if(inputList != null && inputList.size() > 0 ){
                   Recommendation recommendation = new Recommendation();
                   List<String> recommendedStoreIds = new List<String>();
                   List<String> reasonForRecommendations = new List<String>();
                   List<String> input = inputList.get(0);
                   //Last element in input is DateString
                   Date visitRecommendationTargetDate = Date.valueof(input.get(input.size()-1));
                   populateRecommendationsBasedOnCases(input,recommendedStoreIds,reasonForRecommendations);
                   populateRecommendationsBasedOnPromotions(input,visitRecommendationTargetDate,recommendedStoreIds,reasonForRecommendations);
                   populateRecommendationsBasedOnOutOfStockKPIs(input,visitRecommendationTargetDate,recommendedStoreIds,reasonForRecommendations);
                   recommendation.StoreIds = recommendedStoreIds;
                   recommendation.RecommendationReasons = reasonForRecommendations;
                   recommendations.add(recommendation);
               }
               return recommendations;
            }
          
            public static void populateRecommendationsBasedOnCases(List<String> storeIds,List<String> recommendedStoreIds,List<String> reasonForRecommendations) {
              List<RetailStore> stores = [select Id, PrimaryContactId from RetailStore where Id IN :storeIds AND PrimaryContactId != null];
              if(stores!=null){
                  //Check for open cases agianst primarycontact of store.
                  //System.debug('stores found ');
                  Map<String, String> StoreToprimaryContactMap = new Map<String,String>();
                  if(stores.size() > 0){
                    for(RetailStore store : stores){
                      StoreToPrimaryContactMap.put(store.Id,store.PrimaryContactId);
                    }
                
                      List<AggregateResult> casesForPrimaryContactOfStores = [Select contactId, max(createdDate) from Case where isClosed = false AND contactid in :StoreToPrimaryContactMap.values() group by contactId];
                      Map<String, DateTime> primaryContactToCaseCreationDateTimeMap = new Map<String,DateTime>();
                      if(casesForPrimaryContactOfStores != null && casesForPrimaryContactOfStores.size() > 0){
                        for(AggregateResult caseResult : casesForPrimaryContactOfStores){
                          Date caseDate = Date.valueOf(caseResult.get('expr0'));
                          DateTime caseDateTime = DateTime.newInstance(caseDate.year(),caseDate.month(),caseDate.day(),0,0,0);
                          primaryContactToCaseCreationDateTimeMap.put(String.valueof(caseResult.get('ContactId')),caseDateTime);
                        }
                      }
                       //Get latest Visit for PlaceId
                       List< AggregateResult > latestVisitsForStores = [Select PlaceId, max(PlannedVisitStartTime) FROM Visit where PlaceId in :StoreToPrimaryContactMap.keyset() group by PlaceId];
                          if(latestVisitsForStores !=null && latestVisitsForStores.size() > 0){
                             for(AggregateResult visit : latestVisitsForStores){
                               String storeId = String.valueof(visit.get('PlaceId'));
                               String primaryContactId = StoreToprimaryContactMap.get(storeId);
                               //System.debug('primaryContactId found '+primaryContactId+' latest Visit Date for store '+storeId+' is '+visit.get('expr0'));
                               DateTime caseCreationDateTime = primaryContactToCaseCreationDateTimeMap.get(primaryContactId);
                               if(DateTime.valueof(visit.get('expr0')) < caseCreationDateTime){
                                  // No visit is created after case is in open state.
                                  reasonForRecommendations.add('No visit created after the latest case logged against primary contact');
                                  recommendedStoreIds.add(storeId);
                               }
                             }
                             
                          }
                      }
                  }else{
                   System.debug('PrimaryContact is empty for all stores');
                  }
            }
          
            public static void populateRecommendationsBasedOnPromotions(List<String> storeIds, Date visitRecommendationTargetDate, List<String> recommendedStoreIds,List<String> reasonForRecommendations) {
              Integer noOfDaysToConsiderPromotion = 7;
              Date promoStartDate = visitRecommendationTargetDate.addDays(noOfDaysToConsiderPromotion);
              Date promoEndDate = visitRecommendationTargetDate.addDays(365);
              List<PromotionChannel> promotionchannels = [Select Id, RetailStoreId from PromotionChannel where RetailStoreId IN :storeIds AND startDate > :promoStartDate AND (endDate < :promoEndDate OR endDate = null)];
              if(promotionchannels != null){
                for(PromotionChannel promotionChannel : promotionchannels){
                  //upcoming promotion for store
                  //System.debug('promotionchannels for storeId - ' + promotionChannel.RetailStoreId+' is '+promotionchannel);
                  reasonForRecommendations.add('Upcoming Promotion for the store scheduled after '+noOfDaysToConsiderPromotion +' days');
                  recommendedStoreIds.add(promotionChannel.RetailStoreId);
                }
              }
            }
            
            public static void populateRecommendationsBasedOnOutOfStockKPIs(List<String> storeIds,Date visitRecommendationTargetDate,List<String> recommendedStoreIds,List<String> reasonForRecommendations) {
              Integer noOfVisitRecordsForStore = 5;
              Integer noofDaysStoreNotVisited = 14;
              Date actualVisitStartDateForStoreNotVisited = visitRecommendationTargetDate.addDays(-noofDaysStoreNotVisited);
                DateTime actualVisitStartDateTimeForStoreNotVisited = DateTime.newInstance(actualVisitStartDateForStoreNotVisited.year(),actualVisitStartDateForStoreNotVisited.month(),actualVisitStartDateForStoreNotVisited.day(),0,0,0);
                Integer noOfVisitsToCheckForOOS = 1;
                String outOfStock = 'OutOfStock';
                List<Visit> visitsExecuted = [SELECT Id,PlaceId FROM Visit WHERE PlaceId IN :storeIds AND ActualVisitStartTime != null AND ActualVisitStartTime > :actualVisitStartDateTimeForStoreNotVisited];
                List<String> storeIdsCopy = storeIds;
                if(visitsExecuted!= null && visitsExecuted.size() > 0){
                   // visit is already executed for the stores 
                    for (Visit visit : visitsExecuted) {
                      Integer index =storeIdsCopy.indexOf(visit.PlaceId);
                      if(index != -1)
                        storeIdsCopy.remove(index);
                    }
                }
                //System.debug('Visit is not executed for '+storeIdsCopy.size()+' stores');
                Map<String,String> visitIdToStoreIdMap = new Map<String,String>();
                List<String> filteredVisitIdsToCheckOutOfStockKPIS = new List<String>();
                Map<String, List<String>> storeIdToVisits = new Map<String,List<String>>();
                List<Visit> visits = [SELECT Id,PlaceId FROM Visit WHERE PlaceId IN :storeIdsCopy AND ActualVisitStartTime != null ORDER BY ActualVisitStartTime]; 
                  for (Visit visit : visits) {
                    String storeId = visit.PlaceId;
                    visitIdToStoreIdMap.put(visit.Id,storeId);
                    if (storeIdToVisits.get(storeId) == null){
                      List<String> newList = new List<String>();
                      newList.add(visit.Id);
                      storeIdToVisits.put(storeId, newList);
                      filteredVisitIdsToCheckOutOfStockKPIS.add(visit.Id);
                    }else{
                      // add visit id for store only if no. of visits for each store is less than configured #noOfVisitRecordsForStore
                      if(storeIdToVisits.get(storeId).size() <= noOfVisitRecordsForStore){
                         storeIdToVisits.get(storeId).add(visit.Id);
                         filteredVisitIdsToCheckOutOfStockKPIS.add(visit.Id);
                      }
                    }
                  }
                  Map<String,Integer> storeIdToOosKPICount = new Map<String,Integer>();
                  List<RetailVisitKpi> oosRetailVisitkpis= [SELECT Id, Assessmenttask.parentid FROM RetailVisitKpi WHERE Assessmenttask.parentid IN :filteredVisitIdsToCheckOutOfStockKPIS AND Type = :outOfStock AND TargetBooleanValue = 'True'];
                  for (RetailVisitKpi rvkpi : oosRetailVisitkpis) {
                      String visitId = rvkpi.Assessmenttask.parentid;
                      String storeId = visitIdToStoreIdMap.get(visitId);
                      Integer currentStoreIdOOSKPICount = storeIdToOosKPICount.get(storeId);
                      if(currentStoreIdOOSKPICount == null){
                        storeIdToOosKPICount.put(storeId,1);
                      }else{
                        Integer newStoreIdOOSKPICount = currentStoreIdOOSKPICount+1;
                        storeIdToOosKPICount.put(storeId,newStoreIdOOSKPICount);
                        //System.debug('OOS KPI count for '+storeId+' is '+newStoreIdOOSKPICount); 
                      }
                      if(storeIdToOosKPICount.get(storeId) == noOfVisitsToCheckForOOS){
                          reasonForRecommendations.add('Store is not visited in last '+noofDaysStoreNotVisited + ' days and there are atleast '+noOfVisitsToCheckForOOS+ ' Out of stock kpis in previous visits');
                          recommendedStoreIds.add(storeId);
                      }
                  }
            }
          
          
            global class Recommendation {
              @InvocableVariable
              global List<String> StoreIds;
              @InvocableVariable
              global List<String> RecommendationReasons;
            }
          
          }
           
          Loading
          Salesforce Help | Article