8

I'm trying to get through my first SalesForce mod and it's definitely been a rocky path. I have one task I'm trying to do, but this section is holding me back.

I have two custom fields. One a pick list and the other a text box. I have added them to the Approve/Reject Approval Request section in the Opportunity page. I added the fields through the Select Fields to Display on Approval Page Layout and they DO show up on the Approve/Reject Approval Request section. I added them in like (Configuring Approval page layout for user-selectable rejection reason). However, just the label for the fields show, the field itself is nowhere to be found and can't be used.

You can see and even use the fields when you open the Opportunity page itself, but when you go to use the Approve/Reject, you can't do anything with them or see any values.

I was never a SalesForce user and got this tossed in my lap and really don't know my way around yet. I appreciate any help on this!! Thanks!!

Kurt

5
  • Can you go to Setup -> Opportunity -> Fields -> your fields and click "set field level security" on both? I wonder if they're not marked as visible which sometimes causes funny stuff like that even if you're System Administrator and in theory should see all. – eyescream Jun 5 '13 at 16:36
  • Thanks for your suggestion @eyescream. Yep, I checked the Visible check boxes for everyone and have the read only unchecked. This is turning out to be a very strange critter to deal with! Thanks again! Kurt – Kurt Jun 5 '13 at 18:05
  • imgur.com/NHcTYFf. Are you sure the fields have values in them? Or did you expect them to be editable on this page by any chance? This page is a kind of "overview only", readonly except for the comments and actual decision. Any updates should happen either in approval flow itself or the current approver (and anybody with "Modify All Data" right) might be granted the right to edit even if the record is locked. – eyescream Jun 5 '13 at 18:38
  • Thanks for your response @eyescream! How did you do that??? :) Actually, both. I have the list that was created with values that could be selected, and the text box would be for the user to add a comment if they selected Conditional Approval from the list. Just that the fields can't be accessed. That's exactly where I want to have my custom fields, where you have Some custom picklist and text fields. Are those selectable from that screen by the user? Thanks again!!! – Kurt Jun 5 '13 at 18:41
  • 1
    Let's try a chat session maybe, this thing is not exactly a forum... See ya at chat.stackexchange.com/rooms/9118/approval-processes-fun – eyescream Jun 5 '13 at 18:46
3

Out of the box ways (Configuration)

  1. Tell your business users that it can't be done ;) It's "as designed" that the fields on this approve/reject page are readonly. It's purpose is to give some kind of overview. You can grant the right to edit Opportunity to current approver (so he'd have to edit the Opp first, then make the final Approve/Reject decision).

  2. Vote on any of these ideas (that was a quick Google search so there are probably more similar ones, it seems to be a highly demanded feature):

  3. Decide you might want your custom approval process, with tri-state logic (mandatory reference here) Approved / Conditionally Approved / Rejected (& Recalled?), some workflows & validation rule to block all edits performed by anybody other than current approver should do the trick. This depends on whether your users are used to seeing the standard approval process page or not, whether ability to approve via email/Chatter is a must...

  4. With Summer'13 release (~ 10 days away) "Chatter Actions" will become available. If you have Chatter enabled it might be something worth looking at. Haven't read about them in detail but my impression is that they're only for making Tasks now (I was hoping to get some kind of "sniff the body of Comment, if 'Conditional' found, do something).

  5. Maybe I've missed something :) But roughly speaking it seems to be more trouble than it's worth any way you'll attack it.

Code code code (Customisation, since you've said you're on Enterprise Edition)

  1. Inject some javascript to the sidebar - hacky. You'd be sniffing on what kind of page we are, injecting the fields, hijacking the action under the buttons so both the Opportunity approval and update actions fire... Messy solution but it'd appear OK for the user.
  2. Put some buttons on Opportunity that'd say "Approve" "Approve conditionally", "Reject". Probably some javascript & database querying needed to make sure that "current user" is the one who can approve. Makes old page pretty useless, still no nice way to insert comments (JS prompt() function?).
  3. Some trigger maybe (but I think we can't make triggers on Approvals, you'd need some kind of update action on Opportunity that'd hit Opp. trigger... messy again).
  4. Custom Visualforce page for approving. Relatively clean but still - you'd need to instruct users that they have to go to this page and not to the standard one. (one way of doing that would be to modify the Opp. email notification templates, maybe add a button on Opp so they won't be tempted to scroll down to related list of approval history)

tl;dr;

I've had a hack at the last solution as it's most exciting for fellow developers ;) It appears to work OK but I wonder if you want to deep dive into whole coding area on your first assignment, not the best first impression...

Custom Opp Approval

Apex Class

public with sharing class OpportunityApprovalCtrl {
/*  The "with sharing" keyword will prevent unauthorized users from succesfully saving changes to Opp. Only your Approver
    and anybody "godlike" (System Administrators, users with "Modify All Data" permission etc) will be able to approve
    (http://salesforce.stackexchange.com/questions/5377/are).

    If you'd prefer some explicit spelling out of "you don't have permissions, the buttons will be disabled for you" - shout.
*/
    public String approvalComments {get;set;}

    // Mapping between our picklist values and the actions in actual process.
    public final Map<String, String> validActions = new Map<String, String>{
        'Approved' => 'Approve',
        'Conditionally Approved' => 'Approve',
        'Rejected' => 'Reject',
        'Recalled' => 'Remove'
    };

    private ApexPages.StandardController sc;

    public OpportunityApprovalCtrl(ApexPages.StandardController sc){
        if(sc.getId() == null){
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, 'Please pass valid Opportunity Id in the page URL'));
        } else {
            this.sc = sc;
        }
    }

    public PageReference save(){
        try {
            progressApproval();
            return sc.save();
        } catch(Exception e){   
            ApexPages.addMessages(e);
            //  This probably would need some savepoint & rollback (so we don't stay in limbo of "approval OK, Opp update failed")
            //  but let's keep the example simple (lol).
            return null;
        }
    }

    private void progressApproval(){
        Opportunity opp = (Opportunity) sc.getRecord();

        if(!validActions.containsKey(opp.Approval_Status__c)){
            throw new ApprovalException('Unexpected action: ' + opp.Approval_Status__c); // Somebody was messing with the form?
        }
        if(opp.Approval_Status__c == 'Conditionally Approved' && String.isBlank(opp.Approval_Comments__c)){
            throw new ApprovalException( 'It\'s a conditional approval so please add some comments.');
        }

        // First let's fetch the current step in the approval process.
        List<ProcessInstanceWorkItem> items = [SELECT Id 
            FROM ProcessInstanceWorkItem
            WHERE ProcessInstance.Status = 'Pending' AND ProcessInstance.TargetObjectId = :sc.getId()
            LIMIT 1];
        if(items.isEmpty()){
            throw new ApprovalException('Looks like there\'s nothing to approve? Please go back to Opportunity or refresh the page.'); // somebody else has already approved?
        }

        Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
        req.setAction(validActions.get(opp.Approval_Status__c)); // use our mapping to figure out how to translate the action
        req.setComments(String.isBlank(approvalComments) ? opp.Approval_Status__c : approvalComments.abbreviate(4000));
        req.setWorkitemId(items[0].Id);
        Approval.ProcessResult result =  Approval.process(req);
    }

    public class ApprovalException extends Exception{}
}

Visualforce Page

<apex:page standardController="Opportunity" extensions="OpportunityApprovalCtrl" title="Custom Opportunity Approval">
    <apex:sectionHeader title="{!$ObjectType.Opportunity.Label}: {!Opportunity.Name}" />
    <apex:form >
        <apex:pageMessages id="messages"/>
        <apex:pageBlock title="Approve / Reject Approval Request">

            <apex:pageBlockSection columns="1">
                <apex:outputField value="{!Opportunity.Name}" />
                <apex:outputField value="{!Opportunity.OwnerId}" />
                <apex:outputField value="{!Opportunity.AccountId}" />
                <apex:outputField value="{!Opportunity.StageName}" />
                <apex:outputField value="{!Opportunity.Amount}" />
                <apex:inputField value="{!Opportunity.Approval_Status__c}" required="true"/>
                <!-- This is for conditional approval (to store on Opportunity). -->
                <apex:inputField value="{!Opportunity.Approval_Comments__c}"/>  

                <!-- This is for standard comments field (to store in approval history). -->
                <apex:inputTextArea label="Comments" value="{!approvalComments}" cols="100" rows="5" />
            </apex:pageBlockSection>

            <apex:pageBlockButtons location="bottom">
                <apex:commandButton value="Save" action="{!save}" rerender="messages, steps" status="status"/>
                <apex:commandButton value="Cancel" action="{!Cancel}" immediate="true"/>
                <apex:actionStatus id="status" stopText="">
                    <apex:facet name="start">Requesting... <img src="/apexpages/devmode/img/saveStatus.gif" /></apex:facet>
                </apex:actionStatus>
            </apex:pageBlockButtons>
        </apex:pageBlock>
    </apex:form>
    <apex:relatedList id="steps" subject="{!Opportunity.Id}" list="ProcessSteps" />
</apex:page>

You'll also have to add a custom button to Opportunity object to show this page. If the image looks too good to be true - that's because it is ;) You still have to jump some hops - develop in sandbox, write an unit test, deploy to production, grant access for non-System Administrators...

Or maybe I'm completely wrong and there's a flexible way that wouldn't result in such overkill :)

1
  • Thanks @eyescream! Wow, that is a lot of coding. And yep, it looks like from the links, there are many more people than me that would use something like this. How does this sound: If the approval is Approved, and Conditional is saved as the first word in the comments, would there be a way to check a box and make the selected image visible at the top of the page until the Conditional is removed? We're kind of grabbing at straws here. Thank you very much again for your help!! – Kurt Jun 6 '13 at 14:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.