Opportunity Deduplication

Всем привет!

Сегодня хотелось бы поговорить с вами о дедупликации Оппортьюнитей. Вернее об отсутствии оной стандартной функциональности в Salesforce.

Что же нам делать, если есть задача реализовать логику, где по определенным полям оппортьюнити будут определяться как дубликаты? В этой статье рассмотрим один из способов решения данной задачи с помощью lightning component.

Для начала четко обрисуем задачу, которая перед нами стоит:

нужно реализовать функциональность, которая будет демонстрировать юзеру, что оппортьюнити, с которым он работает, имеет дубликаты;
задан список полей, по которому нужно определять, имеет ли данная оппортьюнити дубликаты.

Итак, приступим непосредственно к реализации.

Начнем с написания апекс-контроллер, главной целью которого будет сравнение текущей оппортьюнити со всеми другими оппортьюнитями по определенным полям (в нашем случае это Amount и LeadSource) и формирования списка дубликатов, который мы в будущем будем выводить на сам компонент. Пример контроллера:

global with sharing class OpportunityDeduplicationController {

        @AuraEnabled
        global static List <Opportunity> getDuplicatedOpporunities(String opportunityId) {
            Opportunity currentOpportunity = getCurrentOpporunity(opportunityId);
            List <Opportunity> duplicatedOpportunities  = getDuplicatedOpporunitiesForCurrentOpportunity(currentOpportunity);

            return duplicatedOpportunities;
        }

        @AuraEnabled
        global static Opportunity getCurrentOpporunity(String opportunityId) {     
            
            Opportunity opp = [
                SELECT Id, Name, LeadSource, Amount
                FROM Opportunity
                WHERE Id = :opportunityId
            ];
            
            return opp;
        }

        private static List<Opportunity> getDuplicatedOpporunitiesForCurrentOpportunity(Opportunity opp) {

            List<Opportunity> duplicatedOpps = [
                SELECT Id, Name, LeadSource, Amount
                FROM Opportunity
                WHERE ((Id !=: opp.Id) AND
                    (((LeadSource =: opp.LeadSource) AND (LeadSource != null)) AND
                    (((Amount =: opp.Amount) AND (Amount != null)))))
                ];
        
            return duplicatedOpps;
        }
    }

Далее приступим к написанию lightning компонента и js-контроллера. Этот компонент будет отображаться на странице, если список дубликатов не пустой. Также, добавим красный цвет текста для того, чтобы юзеру сразу бросалось в глаза, что он работает с оппортьюнити, которая имеет дубликаты. Для удобства юзера и для того, чтобы она не занимала много места на экране, секция по умолчанию будет свернута, ибо дубликатов может быть много и каждый раз скроллить — неудобно. Вот, собственно и все. Теперь взглянем на код компонента.

<aura:component controller="OpportunityDeduplicationController" implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

<aura:attribute name="opportunityRecord" type="Opportunity" />
<aura:attribute name="duplicatedOpportunities" type="Opportunity[]" />
<aura:attribute name="url" type="String" />

    <aura:if isTrue="{!not(empty(v.duplicatedOpportunities))}">
        <div class="slds-card" style="background-color: white;">

        <div style="font-size:18px; text-align:center; color:red; margin-top: 5px;">
            Duplication warning
        </div>

        <div class="slds-section slds-is-close" aura:id="collapsibleSection" style="margin: 10px;">
            <h3 class="slds-section__title">
                <button aria-controls="deduplicationOpps" class="slds-button slds-section__title-action" style="margin-bottom: 10px;">
                    <span onclick="{!c.toggleSection}" data-auraId="collapsibleSection">
                        <lightning:icon iconName="utility:switch"
                            size="x-small"
                            class="slds-section__title-action-icon slds-button__icon_left"
                            alternativeText="button icon" 
                            />
                    </span>
                    <span class="slds-truncate"  title="Duplicated Apps:">Duplicated Opportunities</span>
                </button>
            </h3>
        <div class="slds-section__content" id="Opps" style="margin-left: 20px;">
            <aura:iteration var="opportunity" items="{!v.duplicatedOpportunities}">
                <div><a target="_blank" href="{!v.url +'/'+ opportunity.Id}">{!opportunity.Name}</a></div>
                <br></br>
            </aura:iteration>
        </div>

        </div>
        </div>

    </aura:if>
</aura:component>

В связке с компонентом работает и js-контроллер, который имеет два метода. Первый метод взаимодействует с апекс-контроллером и инициализирует список дубликатов значениями в зависимости от того, на каком оппортьюнити вы сейчас находитесь. Второй метод занимается сугубо сворачиванием/разворачиванием нашей секции. Код js-контроллера представлен ниже:

({
    doInit : function(component, event, helper) {
        component.set("v.url", window.location.origin);
        let opportunityId = component.get("v.recordId");
        
        let getOpportunityAction = component.get("c.getCurrentOpporunity");
        getOpportunityAction.setParams({
            "opportunityId": opportunityId
        });        
        getOpportunityAction.setCallback(this, function(response) {
           if (response.getState() === "SUCCESS"){
               component.set('v.opportunityRecord', response.getReturnValue());
           }   
		});
        $A.enqueueAction(getOpportunityAction);
          
        let getDuplicatedOppAction = component.get("c.getDuplicatedOpporunities");
        getDuplicatedOppAction.setParams({
            "opportunityId": opportunityId
        });        
        getDuplicatedOppAction.setCallback(this, function(response) {
           if (response.getState() === "SUCCESS"){
               component.set('v.duplicatedOpportunities', response.getReturnValue());
           }   
		});
        $A.enqueueAction(getDuplicatedOppAction);
    },
    
    toggleSection : function(component, event, helper) {
        var sectionAuraId = event.target.getAttribute("data-auraId");
        var sectionDiv = component.find(sectionAuraId).getElement();
        var sectionState = sectionDiv.getAttribute('class').search('slds-is-open'); 
        
        if(sectionState == -1){
            sectionDiv.setAttribute('class' , 'slds-section slds-is-open');
        }else{
            sectionDiv.setAttribute('class' , 'slds-section slds-is-close');
        }
    }
    })

Закончив с написанием кода, остался один маленький штрих для завершения задания. А именно — вынести компонент на Lightning page в удобное место, где ваш юзер точно не упустит его из виду. По умолчанию конечный вид компонента будет такой -


А при нажатии на “Duplicated Opportunities”— компонент будет следующего вида:


Вот и все. Теперь юзер, работая с оппортьюнити, всегда будет знать, является ли данная оппортьюнити уникальной (по заранее заданным полям) или нет, что позволит предпринимать меры по дедупликации, если в таковых есть необходимость.

Спасибо за внимание, надеюсь эта статья была для вас полезной!

3 Likes

Интересный подход.
Возникло несколько вопросов.

  1. дубликаты мы ищем в апекс контроллере?
  2. поля по которым мы ищем дубликаты захардкожены?
  3. если поля захардкоженые, то как можно гибко и бытро менять полуя для поиска дубликатов?

Заранее спасибо за ответы

  1. Да, ищем в апексе и работаем с этим списком в js.
  2. Да, они захардкожены на этапе выборки дупликатов.
  3. Главная проблема в том, что логика выборки дупликатов может часто меняться. Сейчас это 2 поля, а завтра это может быть 10 полей и совсем другая логика. Поэтому здесь сложно реализовать кастом сеттинги или какие-то другие инструменты, которые помогут избежать хардкодинга. По крайней мере, я их так навскидку, не вижу.

Если есть идеи для улучшения данного функционала, то пишите)

А если мы воспользуемся филд сетом для этого?
Если судить по логике стандартных дубликейшен рулов, то там сравниваются лишь поля и одинаковые занчения в них.
Если мы говрим о кастомной сложной логике, то это уже выходит за понятие дубликейшен рулов в стандартном его исполнении.

С филд сетами хорошая идея. Если имеется стандартная логика сравнения, то филд сеты отлично подойдут. Но опять же. Все зависит от проекта и от того как часто меняются требования)