本文已参与「新人创作礼」活动,一起开启掘金创作之路
场景描述
在审批业务场景中,我们经常会遇到如下场景:
#1. 如果终审人拒审,则必须填入审批留言,说明拒绝原因才允许审批操作;
#2. 如果终审人批准,必须校验画面上某字段必填才能执行批准操作;例如:跨区审批商机,必须由信息管理担当录入对方区域长意见这一字段才能批。
通常网上会建议如下解决方案:
#1. 使用Validation Rule,结合审批操作更新状态和是否字段填写两个维度作批准校验 - 实践知,不可行。原因是状态字段更新是发生在批准ProcessInstance状态更新之后才更新,这一验证规则,只会影响后续批过后DML操作,才会执行。
#2. 基于终审前后分两部分审批流,之前正常提交,第一部分最后一个审批者批准后,发一个Task提醒下一个终审人填写必填字段,之后再基于上一部分审批已批准和字段已填写自动触发最后一个审批流 - 不够好,冗余。
实践演练
场景一Solution:
思路:在对象Before Update时,查询到ProcessInstance中到最后一步Step,如果Comments未填写,拒绝后则使用addError()抛出异常。
Code Sample:
public class ACC_OpportunityTriggerHandler extends ACC_TriggerHandler{
//handle before update logic
public override void beforeUpdate(){
Map<Id, Opportunity> authorizedOppMap = new Map<Id, Opportunity>{};
for(Opportunity opp : ((List<Opportunity>)Trigger.New)){
// collect authorized cross-region opportunity and whose cross region leader opinion is empty
Opportunity oldOpp = (Opportunity)System.Trigger.oldMap.get(opp.Id);
if(!Label.ACC_Opportunity_Status_Authorized.equals(oldOpp.ACC_Approval_Status__c) && Label.ACC_Opportunity_Status_Authorized.equals(opp.ACC_Approval_Status__c) && String.isBlank(opp.ACC_Cross_Region_Leader_Opinion__c) && opp.ACC_Cross_Region__c) {
authorizedOppMap.put(opp.Id, opp);
}
}
if(!authorizedOppMap.isEmpty()) {
System.debug('authorizedOppMap: ' + authorizedOppMap);
ACC_OpportunityTriggerFunction.approvalOpinionCheck(authorizedOppMap);
}
}
}
public class ACC_OpportunityTriggerFunction {
public static void approvalOpinionCheck(Map<Id, Opportunity> authorizedOppMap) {
List<Id> processInstanceIds = new List<Id>{};
for (Opportunity opp : [SELECT (SELECT Id FROM ProcessInstances ORDER BY CreatedDate DESC LIMIT 1)
FROM Opportunity
WHERE Id IN :authorizedOppMap.keySet()]) {
processInstanceIds.add(opp.ProcessInstances[0].Id);
}
for (ProcessInstance pi : [SELECT TargetObjectId, (SELECT Id, StepStatus, Comments FROM Steps ORDER BY CreatedDate DESC LIMIT 1 )
FROM ProcessInstance
WHERE Id IN :processInstanceIds
ORDER BY CreatedDate DESC]) {
if ((pi.Steps[0].Comments == null || pi.Steps[0].Comments.trim().length() == 0)) {
authorizedOppMap.get(pi.TargetObjectId).addError(Label.ACC_Opportunity_Approval_Error_Msg);
}
}
}
}
复制代码
场景二Solution:
思路:使用Trigger,在Before Update时筛选出字段为空,且批准状态由Submitted->Authorized状态的对象List,然后addError()。
Code Sample:
public class ACC_OpportunityTriggerHandler extends ACC_TriggerHandler{
//handle before update logic
public override void beforeUpdate(){
for(Opportunity opp : ((List<Opportunity>)Trigger.New)){
// collect authorized cross-region opportunity and whose cross region leader opinion is empty
Opportunity oldOpp = (Opportunity)System.Trigger.oldMap.get(opp.Id);
if(!Label.ACC_Opportunity_Status_Authorized.equals(oldOpp.ACC_Approval_Status__c) && Label.ACC_Opportunity_Status_Authorized.equals(opp.ACC_Approval_Status__c) && String.isBlank(opp.ACC_Cross_Region_Leader_Opinion__c) && opp.ACC_Cross_Region__c) {
System.debug('devices: ' + UserInfo.getUiThemeDisplayed());
if(Label.ACC_Salesforce_Classic_Theme_Set.contains(UserInfo.getUiThemeDisplayed())) {
opp.addError('<strong style= ' + 'font-size:12pt;color:red;' + '>' + Label.ACC_Opportunity_Approval_Error_Msg + '</strong>', false);
}else {
opp.addError(Label.ACC_Opportunity_Approval_Error_Msg);
}
}
}
}
}
复制代码
效果预览:
细心的朋友不难发现,代码中使用到了:UserInfo.getUiThemeDisplayed()这个方法,其实在实践过程中只有addError()在Classic版本上的UX效果并不令人满意,需要使用一些html tag和css来增强表现力,用法为:addError(‘string contains html tag’, false),但是这在Lightning Experience和Salesforce1上并不兼用,会将含有html的部分作为String展示。
参考资料
UserInfo
spring16 release - rn_vf_uitheme
making-rejection-comments-mandatory-in