Sunday, February 23, 2020

Lightning CPQ button for Recall Approval

Lightning Component:

<aura:component controller="CpqRecallQuoteApprovalController" implements="flexipage:availableForAllPageTypes,force:appHostable,force:lightningQuickActionWithoutHeader,force:hasRecordId">
    <aura:attribute name="isSpinner" type="boolean" default="false"/>
    <aura:handler event="aura:waiting" action="{!c.handleShowSpinner}"/>
    <aura:handler event="aura:doneWaiting" action="{!c.handleHideSpinner}"/>
    <aura:html tag="style">
        .cuf-content {
        padding: 0 0rem !important;
        }
        .slds-p-around--medium {
        padding: 0rem !important;
        }       
        .slds-modal__content{
        overflow-y:hidden !important;
        height:unset !important;
        max-height:unset !important;
        }
    </aura:html>
    <div class="modal-header slds-modal__header slds-size_1-of-1">
        <h4 class="title slds-text-heading--medium" >Recall Approval</h4>
    </div>
    <!-- MODAL BODY / INPUT FORM -->    
    <div class="slds-modal__content slds-p-around--x-small slds-align_absolute-center slds-size_1-of-1 slds-is-relative" aura:id="modalbody" id="modalbody">
        <center>
            <b>This will allow you to edit the quote but will require the quote to be approved again.<br/> 
                Do you wish to continue?
            </b>
        </center>
    </div>
    <div class="modal-footer slds-modal__footer slds-size_1-of-1">
        <div class="forceChangeRecordTypeFooter">
            <ui:button class="slds-button slds-button_neutral" disabled="{!v.isSpinner}" label="Cancel" press="{!c.cancel}" /> 
            <ui:button class="slds-button slds-button--brand" disabled="{!v.isSpinner}" label="{!v.isSpinner == true ? 'Ok...' : 'Ok'}" press="{!c.proceedToRecall}"/>
        </div>
    </div>

</aura:component>

Lightning JS Controller:

({
    cancel: function(component, event, helper) {
        $A.get("e.force:closeQuickAction").fire();
    },
    proceedToRecall: function(component, event, helper) {
        var action = component.get("c.onRecall");
        action.setParams({"quoteId" : component.get("v.recordId")});
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                console.log(response.getReturnValue());
                $A.get('e.force:refreshView').fire();
                $A.get("e.force:closeQuickAction").fire();
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " +
                                    errors[0].message);
                    }
                }
            }
                else {
                    console.log("Unknown error");
                }
        });
        $A.enqueueAction(action);
    },
    //Call by aura:waiting event  
    handleShowSpinner: function(component, event, helper) {
        component.set("v.isSpinner", true); 
    },
     
    //Call by aura:doneWaiting event 
    handleHideSpinner : function(component,event,helper){
        component.set("v.isSpinner", false);
    }

})

Apex Controller:

public class CpqRecallQuoteApprovalController {
    
/*
* This method will call get triggered on Recall approval button in Lightning Component
*/ 
    @AuraEnabled
    public static void onRecall(String quoteId) {
        if (quoteId != null) {
            SBAA.ApprovalAPI.recall(quoteId, SBAA__Approval__c.Quote__c);
        }
    }

}

Lightning CPQ button for Submit For Approval

Lightning Component:


<aura:component controller="CpqQuoteApprovalController" implements="flexipage:availableForAllPageTypes,force:appHostable,force:lightningQuickActionWithoutHeader,force:hasRecordId">
    <aura:attribute name="requiredValuesNotAvailable" type="boolean" default="true"/>
    <aura:attribute name="displaySecondConfirmation" type="boolean" default="false"/>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <aura:attribute name="isSpinner" type="boolean" default="false"/>
    <aura:handler event="aura:waiting" action="{!c.handleShowSpinner}"/>
    <aura:handler event="aura:doneWaiting" action="{!c.handleHideSpinner}"/>
     
    <aura:html tag="style">
        .cuf-content {
        padding: 0 0rem !important;
        }
        .slds-p-around--medium {
        padding: 0rem !important;
        }       
        .slds-modal__content{
        overflow-y:hidden !important;
        height:unset !important;
        max-height:unset !important;
        }
    </aura:html>
    <div class="modal-header slds-modal__header slds-size_1-of-1">
        <h4 class="title slds-text-heading--medium" >Submit For Approval</h4>
    </div>
    
    <!-- MODAL BODY / INPUT FORM -->    
    <div class="slds-modal__content slds-p-around--x-small slds-align_absolute-center slds-size_1-of-1 slds-is-relative" aura:id="modalbody" id="modalbody">
        <aura:if isTrue="{!!v.displaySecondConfirmation}">
            <center>
                <b>Do you want to submit quote for approval?<br/>
                    Once submitted for approval Quote will be locked.</b>
            </center>
        </aura:if>
        <aura:if isTrue="{!v.displaySecondConfirmation}">
            <center>
                <b>Please provide Quote Name and Discount Justification before Submitting for Approval</b>
            </center>
        </aura:if>
    </div>
    <!-- End of Modal Content -->  
    <!-- MODAL FOOTER -->
    <div class="modal-footer slds-modal__footer slds-size_1-of-1">
        <aura:if isTrue="{!!v.displaySecondConfirmation}">
            <div class="forceChangeRecordTypeFooter">
                <ui:button class="slds-button slds-button_neutral" disabled="{!v.isSpinner}" label="Cancel" press="{!c.cancel}" /> 
                <ui:button class="slds-button slds-button--brand" disabled="{!v.isSpinner}" label="{!v.isSpinner == true ? 'Ok...' : 'Ok'}" press="{!c.proceedToSubmit}"/>
            </div>

        </aura:if>
        <aura:if isTrue="{!v.displaySecondConfirmation}">
            <ui:button class="slds-button slds-button--brand" label="Ok" press="{!c.cancel}" />
        </aura:if>
    </div>

</aura:component>


Lightning JS Controller:

({
    doInit: function(component, event, helper) {
        var action = component.get("c.checkForRequiredFields");
        action.setParams({"recordId" : component.get("v.recordId")});
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                console.log(response.getReturnValue());
                component.set("v.requiredValuesNotAvailable", response.getReturnValue());
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " +
                                    errors[0].message);
                    }
                }
            }
                else {
                    console.log("Unknown error");
                }
        });
        $A.enqueueAction(action);
    },
    cancel: function(component, event, helper) {
        $A.get("e.force:closeQuickAction").fire();
    },
    proceedToSubmit: function(component, event, helper) {
        var requiredValuesNotAvailable = component.get("v.requiredValuesNotAvailable");
   
        if(requiredValuesNotAvailable){
            component.set('v.displaySecondConfirmation', true);
        }
        else{
            var action = component.get("c.onSubmit");
            action.setParams({"quoteId" : component.get("v.recordId")});
            action.setCallback(this, function(response) {
                var state = response.getState();
                if (state === "SUCCESS") {
                    console.log(response.getReturnValue());
                    $A.get('e.force:refreshView').fire();
                    $A.get("e.force:closeQuickAction").fire();
                }
                else if (state === "ERROR") {
                    var errors = response.getError();
                    if (errors) {
                        if (errors[0] && errors[0].message) {
                            console.log("Error message: " +
                                        errors[0].message);
                        }
                    }
                }
                    else {
                        console.log("Unknown error");
                    }
            });
            $A.enqueueAction(action);
         
        }
    },

     //Call by aura:waiting event
    handleShowSpinner: function(component, event, helper) {
        component.set("v.isSpinner", true);
    },
   
    //Call by aura:doneWaiting event
    handleHideSpinner : function(component,event,helper){
        component.set("v.isSpinner", false);
    }
})

Apex Controller:

public class CpqQuoteApprovalController {
    @AuraEnabled
    public static boolean checkForRequiredFields(String recordId) {
        Boolean requiredFieldsNotAvailable = false;
        List<SBQQ__Quote__c> qt = [SELECT Id, SBQQ__Type__c, Quote_Name__c, Discount_Justification__c FROM SBQQ__Quote__c Where Id =: recordId];
        if((qt[0].SBQQ__Type__c == 'Amendment') && ((qt[0].Quote_Name__c == null) || (qt[0].Discount_Justification__c == null))){
            requiredFieldsNotAvailable = true;
        }
        return requiredFieldsNotAvailable;
    }
/*
* This method will call submit approval method
*/
    @AuraEnabled
    public static void onSubmit(String quoteId) {
        if (quoteId != null) {
            SBAA.ApprovalAPI.submit(quoteId, SBAA__Approval__c.Quote__c);
        }
    }
}

Sunday, October 20, 2019

Enforce Field-Level Security Permissions for SOQL Queries

This post will explain how to enforce field level security permissions for SOQL queries,

  • In Spring' 19 release salesforce introducing this fantastic feature, WITH_SECURITY_ENFORCED clause, you can use this to enable checking for field- and object-level security permissions on SOQL SELECT queries, including subqueries and cross-object relationships.
  • If you include a field in a SQOL query (Without WITH SECURITY_ENFORCED Clause) and a user doesn’t have access to that field, the field will be returned and can be used by the code without any exception, but the data to that user should not have access.
  • If you use WITH SECURITY_ENFORCED clause for same SOQL Select query, it will throw exception and no data will be returned.
  • The WITH SECURITY_ENFORCED clause is only available in Apex. Using WITH SECURITY_ENFORCED in Apex classes or triggers with an API version earlier than 45.0 is not recommended.

Below are some of the examples.

1. If on the Opportunity object, suppose field-level security for  Stage , Amount, and LeadSource is hidden, it will throw an exception insufficient permissions and no data will return.

String query = ’Select Id, Name, ‘;
if(Schema.sObjectType.Opportunity.fields.Amount.isAccessible()){
query += ‘Amount, ’;
} else{
//Exception handling code will go here
}
if(Schema.sObjectType.Opportunity.fields. LeadSource.isAccessible()){
query += ‘LeadSource, ’;
}
if(Schema.sObjectType.Opportunity.fields.StageName.isAccessible()){
query += ‘StageName, ’;
}
query = query.subString(0, query.length()-2);
query += ‘ FROM Opportunity’;
List<Opportunity> oppty = Database.query(query);
And after this awesome feature introduced, developers need to write below lone of code.
List<Opportunity> oppty = [SELECT Id, Name, LeadSource ,StageName, Amount FROM Opportunity WITH SECURITY_ENFORCED];

2. If field access for Website is hidden, this query throws an exception indicating insufficient permissions.

List<Account> acct = [SELECT Id, parent.Name, parent.Website
FROM Account WITH SECURITY_ENFORCED]

How to use in Apex Method

In below code example, the comparison can be understand that how the field
accessibility were getting checked before introduction of this awesome feature.

Checking field accessibility before this feature:

 if (Schema.SObjectType.Contact.isAccessible()
            && Schema.SObjectType.Contact.fields.Name.isAccessible()
            && Schema.SObjectType.Contact.fields.Secret_Key__c.isAccessible()){
            List results = [SELECT id, Name, Secret_Key__c FROM Contact WHERE Id = :recordId];
            if (!results.isEmpty()) {
                result = results[0];
            }
        } else{
        throw new SecurityException('You don\'t have access to all contact fields'); }

Checking field accessibility after this feature:

try
{
List results = [SELECT id, Name, Secret_Key__c FROM Contact WHERE Id = :recordId 
WITH SECURITY_ENFORCED];
if (!results.isEmpty()) {
result = results[0];
}
}
catch( System.QueryException exception)
{
     throw new SecurityException('You don\'t have access to all contact fields'); }

Friday, October 19, 2018

Automated Code review for Apex in Salesforce – Static code analysis


PMD is a source code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth. It supports Java, JavaScript, Salesforce.com Apex and Visualforce, PLSQL, Apache Velocity, XML, XSL.

Installation Steps:

1. For MacOS:

Follow below steps to install PDM.

A. Open the terminal
B. Run cd $HOME
C. Run curl -OL https://github.com/pmd/pmd/releases/download/pmd_releases%2F6.8.0/pmd-bin-6.8.0.zip
D. unzip pmd-bin-6.8.0.zip
E. alias pmd="$HOME/pmd-bin-6.8.0/bin/run.sh pmd"

2. For Windows:

Follow below steps to install PDM.

A. Open the command prompt
B. Download pmd-bin-6.8.0.zip 
C. Extract the zip-archive, e.g. to C:\pmd-bin-6.8.0
D. Add folder C:\pmd-bin-6.8.0\bin to PATH, either
Permanently: Using System Properties dialog > Environment variables > Append to PATH variable
Temporarily, at command line: SET PATH=C:\pmd-bin-6.8.0;%PATH%
E. alias pmd="$HOME/pmd-bin-6.8.0/bin/run.sh pmd"

PMD Configuration for Eclipse :

To install the PMD plugin for Eclipse:

A. Start Eclipse and open a project
B. Select “Help”->“Install New Software”->“Find and Install”
C. Click “Add”
D. Enter “PMD” into the Name field and http://sourceforge.net/projects/pmd/files/pmd-eclipse/update-site/ into the URL field
E. Click “Ok”
F. Click through the rest of the dialog boxes to install the plugin


Running PMD on Eclipse :

A. To run PMD, right-click on a project node and select “PMD”->“Check code”.
B. To run the duplicate code detector, right-click on a project node and select “PMD”->“Find suspect cut and paste”. The report will be placed in a “reports” directory in a file called “cpd-report.txt”.

Wednesday, May 9, 2018

Build URL to display a case using case number instead of Salesforce CaseID

This can be implemented using below code.

Custom label:  
Name: Case_Number_Details

Value: Please enter the valid 8 character Case Number (e.g. 00123456) after the equal sign in the URL above to view the case details.


Visualforce Page:


<apex:page controller="GetCaseByNumber" title="Get Case By Number" showHeader="false">

  <script>

      if ("{!CaseQuery}".length > 0) {

          if ("{!CaseQuery}" != "<apex:outputText value="{!$Label.Case_Number_Details}"/>" && "{!CaseQuery}" != "You Must Supply a Case Number") window.location = "/{!CaseQuery}";

      }

  </script>

  <h1 style="display:block;margin:25px;font-size:16px;">{!CaseQuery}</h1>


</apex:page>


Apex Class:


public class GetCaseByNumber {

    public static String getCaseQuery () {
       String caseNumberDetails = Label.Case_Number_Details;

        string caseNumber = ApexPages.CurrentPage().getParameters().get('CaseNumber');
        List <Case> c = [SELECT Id FROM Case Where CaseNumber =: caseNumber];
        if (c.size()>0) {
            return string.valueOf(c[0].Id);
        }
        else return caseNumberDetails;

    }    

}

Apex test Class:



private class GetCaseByNumberTest {
    
    static testmethod void TestAccdata(){ 
        Profile prof = [select id from profile where name='System Administrator'];    
        User us      = new User(alias = 'tuser', email='TestUser@testingorg.com',    
                                emailencodingkey='UTF-8', lastname='TestingUser', languagelocalekey='en_US', 
                                localesidkey='en_US', profileid = prof.Id,   
                                timezonesidkey='America/Los_Angeles', username='TestUser@testingorg.com');
        insert us;  
        System.runAs (us) { 
            Account ac = new Account(Name    ='Test', OwnerId  =us.Id);            
            insert ac;
            
            
            Contact cn  = new Contact (FirstName  = 'Test', LastName = 'Test',AccountId = ac.Id,email='testcase@org.com');
            insert cn;
            
            Test.startTest();
            string RTID = [select id from RecordType where SobjectType = 'Case' and Name = 'Support Case'].Id;
            System.assertNotEquals(null,RTID);
            
            case c = new Case(AccountId=ac.Id ,ContactId =  cn.Id,Status='New',Origin='Phone',RecordTypeId = RTID,Subject = 'TestCase',Description ='Test',Product_Type__c = 'My Product);
            
            insert c;
            PageReference myVfPage = Page.getCaseByNumber;
            Test.setCurrentPage(myVfPage);
           ApexPages.currentPage().getParameters().put('CaseNumber',ApexPages.currentPage().getParameters().get(c.CaseNumber));
            
            GetCaseByNumber controller = new GetCaseByNumber();
            GetCaseByNumber.getCaseQuery();
            
            Test.stopTest();
            
        }

    }