Salesforce中常用技能总结(纯粹干货,深度积累)图解

时刻分享,时刻感恩!
131、【使用String.format(template, args)处理邮件模板占位】:
效果预览:

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第1张图片

代码示例:

String template = '{0}, has updated the following new task: \n\n';
template+= 'Subject - {1}\n';
template+= 'Status - {2}\n';
template+= 'Priority - {3}\n';

List args = new List();
//args.add(tsk.LastModifiedBy.name);
//args.add(tsk.Subject);
//args.add(tsk.Status);
//args.add(tsk.Priority);

args.add('Tom');
args.add('Lead01');
args.add('Completed');
args.add('Normal');

String formattedHtml = String.format(template, args);
System.debug('......formattedHtml: ' + formattedHtml);

拓展示例:

public static String SOQL_TEMPLATE = 'SELECT {0} FROM {1} WHERE {3} in :masterIds {2} GROUP BY {3}';

String soql = String.format(SOQL_TEMPLATE, new String[]{'COUNT(Id)', 'Contact', 'AccountId', 'LeadSource'});
System.debug('SOQL is ' + soql);

130、【SOQL中使用NOT LIKE】:
用例:比如想查看sandbox中哪些Active的User的Email不包含invalid,以便添加invalid标识,这时需用到NOT LIKE;
用法:(NOT Field__c LIKE '%invalid')

List uList = [SELECT Id, Name, Email, UserRole.Name FROM User WHERE IsActive = TRUE AND (NOT Email LIKE '%invalid') ORDER BY Email];

129、【初识OpenActivity & ActivityHistory】:OpenActivity | ActivityHistory
a. 这两个对象包含Task和Event两部分数据;
b. 这两个对象只读,不能更新字段(不能编辑),也不能删除
c. 这两个对象不支持单独查询,必须使用父查子的方式才能查询:示例如下:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第2张图片

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第3张图片

128、【Send Notification when Task Created From Apex】:DmlOptions.EmailHeader Class | DMLOptions does not work | Task Email Notifications via APEX not working, could be a Bug in platform?

List myTasks = new List();
for(integer i = 0; i < 5; i ++){
    Task newTask = new Task();
    newTask.Subject='Automated Task';
    newTask.OwnerId = ;
    myTasks.add(newTask);
}
Database.DMLOptions notifyOption = new Database.DMLOptions();
notifyOption.EmailHeader.triggerUserEmail = true;

//we need to use the Database.insert() with the DMLOptions to insert the task instead of using the standard insert DML command
Database.insert(myTasks, notifyOption);

使用经验:如果套用上述方法创建Task后没收到Email,可能是Task的Statuis设定为:Complete了。因此,请务必确保Task在Open Activities非Activity History栏,这时该Owner才能收到通知。另外,Email中发件人视环境而定(Internal -> 代码上次修改人;Community -> Community Administration中的Email),Email Body包含Task标准字段信息:Subject,WhoId,Priority,Comments等标准字段信息,若Comments没值,不会显示。

127、【为Classic的Internal User启用Global Header for Communities】:官方Article | 博客
目的:make internal user to easily switch back and forth between internal and the external communities.
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第4张图片

126、【RecentlyViewed对象】:

SELECT Id, Name FROM RecentlyViewed WHERE Type ='Account'

125、【JS文件下载】:JS弹出下载对话框以及实现常见文件类型的下载

124、【Custom URL Button for Community】:Creating Custom Button Code for Partner Communities & Salesforce Internal 
场景:需要在Community中应用URL自定义Button,并且URL不受环境影响 - 避免Hard Code。
方案1Sample:{! URLFOR($Site.Prefix + '/apex/FinancialPlanningForm?rid=' + Lead.Id) }
方案2Sample:{! URLFOR( $Label.DST_Community+"/FinancialPlanningForm?rid="+Lead.Id ) }
评价:方案1在Lightning当前页面打开,嵌套在当前页;方案2不管Console/Internal/Community都用指定都Community Domian打开。对于类似业务,更倾向使用方案2
其他资源:Allow URLFOR() in Formula Fields to Support Consoles and Communities

123、【File Upload and Download Security】:Configure the setting 'File Upload and Download Security'
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第5张图片

122、【根据记录ID获取Sobject Type的2种方法】:

Id recId = '00Q0l000005pF5A';
String sobj1 = String.valueOf(recId.getSObjectType());
String sobj2 = recId.getSObjectType().getDescribe().getName();

121、【Pass URL Parameters to remote action in apex】:
不能从Apex中标记Remote Action的方法里边获取URL参数,因为Remote Action是无状态的。
解决思路:从VFP的JS中获取参数并通过JS Remoting传参到该方法;

120、【Organization对象】:

SELECT Id, Name, Country, DefaultLocaleSidKey, TimeZoneSidKey, LanguageLocaleKey, 
       ReceivesInfoEmails, ReceivesAdminInfoEmails, 
       UiSkin, TrialExpirationDate, OrganizationType, NamespacePrefix, 
       InstanceName, IsSandbox, IsReadOnly, 
       MonthlyPageViewsUsed, MonthlyPageViewsEntitlement 
FROM Organization

119、【新建Org My Domain默认设置问题】:
问题:在19年11月22日时,公司新购买了一个Org,突然发现My Domain已经被初始化了。
思维定势:早前我们知道,一旦My Domain被设定后,就不能更改。
解决方案:不要担心,初始化的My Domain是可以被修改的,这是Salesforce为了推广Lightning的一种方式。
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第6张图片

118、【App and Tab Describe Information】:示例

List dtsrList = Schema.describeTabs();
for(DescribeTabSetResult dtsr : dtsrList){
    String appLabel = dtsr.getLabel();
    System.debug('App label: ' + appLabel);
    
    if(appLabel == 'Sales'){
        List dtrList = dtsr.getTabs();
        for(Schema.DescribeTabResult dtr : dtrList){
            System.debug('Tab label: ' + dtr.getLabel());
            System.debug('Tab icon url: ' + dtr.getIconUrl());
            System.debug('Tab url: ' + dtr.getUrl());
        }
    }
}

117、【sf标准报表定时导出到第三方文件服务器】:

String rid = [SELECT Id, Developername FROM Report WHERE Developername = 'ContactExportReport' LIMIT 1].Id;
PageReference file = new PageReference('/'+rid+'?export=1&enc=UTF-8&xf=csv');
Http http = new Http();
            
HttpRequest request = new HttpRequest();
string sURL=label.File_Server_Domain+'/hornet/backup2?suffix=csv&module='+umoduleName;
request.setEndpoint(sURL);
request.setMethod('POST');
request.setHeader('Content-Type','application/csv');
if(!test.isrunningtest()) request.setBodyAsBlob(file.getcontent());

request.setTimeout(120000);
            
HttpResponse response = http.send(request);

Map result = (Map)JSON.deserializeUntyped(response.getBody());


116、【获取当前时间所属的星期简写】:Sun / Thu

datetime.now().format('E');

115、【Salesforce Stock Images】:Sample Image Link Formulas
該類資源可以不經login校驗,直接被使用:https://slds-deloitte-dev-ed.my.salesforce.com/img/samples/light_green.gif
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第7张图片
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第8张图片
114、【Dynamic Query -  String Join的用法】:join(iterableObj, separator)

List fNames = new List{'Name', 'Industry', 'Type'};
String objectName = 'Account';
String query = 'SELECT Id'+ (fNames.size() > 0 ? ',' + String.join(fNames,',') : '') +' FROM '+ objectName;
System.debug(query);

113、【关于修改User Email】:
Partner User: 直接修改无需验证;
Internal User:需要新邮箱确认才能被修改成功;

112、【获取sandbox name / community站点URL】:

ConnectApi.CommunityPage cc = ConnectApi.Communities.getCommunities();
System.debug(cc.communities[0].siteUrl);

111、【禁用双重身份验证】:
第一次登录,输入用户名密码点登录就会要求输入验证码,可用以下方法解决:
方法1:设置IP安全范围0.0.0.0 - 255.255.255.255;
方法2:在Profile里面禁用以下开关:


110、【数值加千分符方法】:Decimal / Integer / Double / Long.format(); | Salesforce Apex各大数据类型Format工具类模板

109、【User上的Mobile User字段API】:
我们知道User表上有个Mobile User的checkbox,它的API Name不是UserPermissionsMobileUser而是UserPreferencesHideS1BrowserUI;
如果Mobile User为true,那么UserPreferencesHideS1BrowserUI查出的结果为false,值刚好想反;

108、【Date / DateTime Format公共方法】:

/*---------Demo--------*/
System.debug(String.valueOf(System.now().format('yyyy/MM/dd hh:mm')));

Date dt = Date.today().addDays(15);
Datetime dtDateTime = dt; // Implicit cast
System.debug(dtDateTime.format('yyyy/MM/dd'));


/*---------公共方法--------*/
public static String dateOrTimeFormat(String type, Object dateOrTime) {
    Datetime dt = (Datetime) dateOrTime;
    return 'Date'.equals(type) ? dt.format('yyyy/MM/dd') : dt.format('yyyy/MM/dd HH:mm');
}

/*---------应用--------*/
Test__c t = [SELECT Id, Date__c, DateTime__c FROM Test__c WHERE Id = 'a0F7F00000DdKfe'];
System.debug('before convert...');
System.debug(t.Date__c);
System.debug(t.DateTime__c);

System.debug('after convert...');
Datetime dtDateTime = t.Date__c;
System.debug(UtilityClass.dateOrTimeFormat('Date', dtDateTime));
System.debug(UtilityClass.dateOrTimeFormat('DateTime', t.DateTime__c));

效果预览:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第9张图片

107、【Password - 修改Email及重置密码】:
修改密码:
Salesforce License User: 确认邮件将发送到新邮箱,邮箱确认后,修改生效。
Partner Community License User: 直接修改,无确认修改邮件。

重置密码:
一旦用户或为用户重置密码后,需要通过邮箱链接重置密码。

106、【Dynamic Query - 获取父查子类型的子元素】:

String query = 'SELECT Id, Name, (SELECT Id, Name FROM Contacts) FROM Account LIMIT 2';
List parents = Database.query(query);
for(SObject parent : parents) {
    for(SObject child : parent.getSObjects('Contacts')) {
        system.debug('Child=' + child);
    }
}

项目实战
Case1: 有子记录:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第10张图片

Case2: 无子记录:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第11张图片

105、【String.leftPad(length, strNo)用法】:Salesforce数字字符串移位 - Sample:00520

String countString = '520';
System.debug(countString.leftPad(5, '0'));

104、【如何使用Custom Label自定义标间 - 换行】:


103、【将Set转成List合并使用102 Demo】:

Set codes = new Set{'001', '002', '003'};
//String mergeSet = String.join(codes, ';');// 语法错误
String mergeStr = String.join(new List(codes), ';');
//System.debug(mergeSet);
System.debug(mergeStr);


102、【将List转成按分隔符隔开的String】:join

List sl = new List {'a','b','c'};
system.debug(sl);
String s = String.join(sl, ';');
system.debug(s);

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第12张图片

101、【将List集合转成Set集合】:

List sLst = new List {'1', '2', '1'};
System.debug(new Set (sLst));

100、【Company Profile的Fiscal Year在SOQL中的应用】:前往

99、【Using Attachment to Storage Logs - Attachment的存与取】:
Case:在做数据集成(类似Data Backup那种)时,非增量的同步往往会超出callout限制,如异步body size限制12MB,同步6MB,这个时候同步到第三方由于数据不合规被skip掉的也不少,之前在没有确定竟然是全量同步的情况下,本身使用自定义对象来存储log,后面skip掉的那些log直接将长文本13w+个字符限制都给超了,无奈在同事的建议下放弃了加字段来分开存储的可能,直接上了Attachment来存,这个可能是Best Practice。
Sample
加入存储的exception log长下面这样:

15 Listing(s) Skipped - {"a060k000005XppaAAC":["Listing's [E&V ID] doesn't start with 'EVHK': 是的發生分散","Listing's [Description] invalid content length: For description at least 30 words and maximum 2500 words"],"a060k000005fAKjAAM":["Listing's [E&V ID] doesn't start with 'EVHK': 435325","Listing's [Property Type](Complexd) doesn't match the property type those set in Squarefoot: Apartment公寓, Stand-alone building 單橦式大廈, Estate 屋苑, Public / HOS flat 公共屋邨 /居屋, Village house 村屋, House / Villa 獨立屋 / 洋房 / 別墅, Car park 停車場, Boat遊艇 / 船.","Listing's [Description] is missing"],"a060k000005YYwoAAG":["Listing's [E&V ID] doesn't start with 'EVHK': sdfsfsf","Listing's [Property Type](Complexd) doesn't match the property type those set in Squarefoot: Apartment公寓, Stand-alone building 單橦式大廈, Estate 屋苑, Public / HOS flat 公共屋邨 /居屋, Village house 村屋, House / Villa 獨立屋 / 洋房 / 別墅, Car park 停車場, Boat遊艇 / 船.","Listing's [Description] is missing"],"a060k000005fC7cAAE":["Listing's [Property Type] is missing","Listing's [Description] is missing"],"a060k000005LopTAAS":["Listing's [E&V ID] doesn't start with 'EVHK': 33252","Listing's [Property Type] is missing","Listing's [Description] is missing"],"a060k000005fBtQAAU":["Listing's [E&V ID] doesn't start with 'EVHK': 34534","Listing's [Description] is missing"],"a060k000005MCLfAAO":["Listing's [Description] is missing"],"a060k000005fDTzAAM":["Listing's [E&V ID] doesn't start with 'EVHK': 324566","Listing's [Description] is missing"],"a060k000005eWhPAAU":["Listing's [E&V ID] doesn't start with 'EVHK': 23552352435235","Listing's [Property Type] is missing","Listing's [Description] invalid content length: For description at least 30 words and maximum 2500 words"],"a060k000005f9spAAA":["Listing's [E&V ID] doesn't start with 'EVHK': 1233321","Listing's [Description] is missing"],"a060k000005fBtaAAE":["Listing's [E&V ID] doesn't start with 'EVHK': 4333228","Listing's [Description] is missing"],"a060k000005dAThAAM":["Listing's [Property Type](Complexd) doesn't match the property type those set in Squarefoot: Apartment公寓, Stand-alone building 單橦式大廈, Estate 屋苑, Public / HOS flat 公共屋邨 /居屋, Village house 村屋, House / Villa 獨立屋 / 洋房 / 別墅, Car park 停車場, Boat遊艇 / 船.","Listing's [Description] invalid content length: For description at least 30 words and maximum 2500 words"],"a060k000005XpjEAAS":["Listing's [E&V ID] doesn't start with 'EVHK': ewetw","Listing's [Description] is missing"],"a060k000005fAQhAAM":["Listing's [E&V ID] doesn't start with 'EVHK': 23423","Listing's [Description] is missing"],"a060k000005cmyGAAQ":["Listing's [Property Type] is missing","Listing's [Description] is missing"]}

关于存:那么我们使用Attachment存只需要使用下面几个关键字段:Name/ParentId/Body(blob)。

List logs = [SELECT Id, Excepition__c  FROM Integration_Log__c WHERE Id = 'a080k00000DPLgG' LIMIT 1];
System.debug(logs[0].Excepition__c.length());
String content  = logs[0].Excepition__c;
Attachment att = new Attachment();
att.Parentid = 'a080k00000DPLgG';
att.Name = 'SfdcSyncListingsTosquarefoot partial success!';
att.Body = blob.valueOf(content);
insert att;

关于取:我们将Blob使用Blob.toString()转以下就好了。

List atts = [SELECT Id, ParentId, Name, ContentType, Body, Description FROM Attachment WHERE Id = '00P0k000002yNQm'];
Blob contentBlob = atts[0].Body;
String str = contentBlob.toString();
System.debug(str);

98、【获取Salesforce User默认图片技巧】:
domain + profilephoto/005/T
获取后如下:T(45 x 45)
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第13张图片

97、【Apex Email编码问题处理】:
在使用Apex发Email的时候,常常会忽视编码,很明显,下图中文部分变形了:
------------------------------------------------------------------------------------------------
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第14张图片
------------------------------------------------------------------------------------------------
解决方法mail.setCharset('UTF-8');
解决后review:
------------------------------------------------------------------------------------------------
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第15张图片
------------------------------------------------------------------------------------------------

96、【Schedule a Job every 10min】:每天10分钟跑一次Schedule Job
当我们在按分制定周期性计划时,如下图:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第16张图片
会出现如下错误:
System.StringException: Seconds and minutes must be specified as integers: 0 0/10 * * * ? *
解决方案如下

/**********************周期性执行*********************/
ScheduledListingSyncToEVJob sc1 = new ScheduledListingSyncToEVJob();
String cronExp1 = '0 0 * * * ?'; 
String jobID1 = System.schedule('ScheduledListingSyncToEVJob0', cronExp1, sc1);

ScheduledListingSyncToEVJob sc2 = new ScheduledListingSyncToEVJob();
String cronExp2 = '0 10 * * * ?'; 
String jobID2 = System.schedule('ScheduledListingSyncToEVJob10', cronExp2, sc2);

ScheduledListingSyncToEVJob sc3 = new ScheduledListingSyncToEVJob();
String cronExp3 = '0 20 * * * ?'; 
String jobID3 = System.schedule('ScheduledListingSyncToEVJob20', cronExp3, sc3);

ScheduledListingSyncToEVJob sc4 = new ScheduledListingSyncToEVJob();
String cronExp4 = '0 30 * * * ?'; 
String jobID4 = System.schedule('ScheduledListingSyncToEVJob30', cronExp4, sc4);

ScheduledListingSyncToEVJob sc5 = new ScheduledListingSyncToEVJob();
String cronExp5 = '0 40 * * * ?'; 
String jobID5 = System.schedule('ScheduledListingSyncToEVJob40', cronExp5, sc5);

ScheduledListingSyncToEVJob sc6 = new ScheduledListingSyncToEVJob();
String cronExp6 = '0 50 * * * ?'; 
String jobID6 = System.schedule('ScheduledListingSyncToEVJob50', cronExp6, sc6);


95、【Remote Site Setting】:
如果站点是ip,且含端口号的,需要一并将端口号加上,不能仅仅添加ip。

94、【Email-to-Case那点坑】:
在做如下需求时:

email 2 case

TARGET:
do search in both contact and lead and populate the lead or contact value on case.

LOGIC:
1. email match sigle client -> relate to client
2. email match sigle lead   -> relate to lead
3. email match more than one lead or client or  -> not relate
4. no match lead or contact -> no relate

RECORDTYPE:
case      -> Enquiry Case Record Type
contact   -> all

Note:
SuppliedEmail on case will be the end client's email;
End clients will send email to the service email not the real email when using email to case;

Trigger Logic Design:
1. before insert

遇到了一个坑,当单个Lead和单个Contact的Email都相同时,根据上述逻辑3, 理应Account/Contact值不应被填充,发现神奇的被填充上了,当时检查了原有的所有apex code和workflow/flow/process builder,都没发现有这个逻辑,最后在同事Don的帮助下,最终解决了这个问题。

原因sf标准的Email-to-Case,如果有一个Contact Email匹配上了邮件发送人的Email,就会自动填充Account和Contact;如果有多个Contact都含有相同的Email,那么就不填充。所以上述逻辑中尽管单个Lead和单个Contact的Email都相同,但是由于Contact为单个的缘故,执行了标准功能的自动填充。

解决方案:在before insert trigger的else语句中清空Account和Contact即可。

93、【使用count()计数统计记录】:
我们在使用SOQL时,用的最多的是select count() from sObject,这种形式默认是根据记录Id来进行聚合统计,那假如我们使用count(field api name),我们就能够统计某字段值不重复的所有记录数了,如下是我们统计系统中Lead的Phone唯一的记录数的code snippets:

SELECT count(Phone) FROM Lead


92、【访问Metadata Coverage Report】:https://yourOrgUrl/mdcoverage/report.jsp
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第17张图片

91、【工具类中获取当前用户信息的两种用法】:UserInfo

public static User currentUser = [SELECT Id, Name, Email, Contact.Phone, Profile.Name FROM User WHERE Id = :UserInfo.getUserId() LIMIT 1];
Map tmap = new Map(currentUser.getPopulatedFieldsAsMap());
tmap.put('Oid', UserInfo.getOrganizationId());

90、【Admin Log in as Any Uses】:Admin
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第18张图片
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第19张图片


89、【为Campaign启用New按钮】:Admin | New Campaign

针对上图,只需要为该User启用Marketing User即可。


88、【系统管理员默认Setup Landing Page】:Admin
My Settings -> Advanced User Details -> Make Setup My Default Landing Page
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第20张图片

87、【Personal Account相关问题总结】:
a、不能直接删除Contact,必须删除Account才能将Account和Contact一起删掉;

86、【在公式里面获取Org URL】:
如:需要在画面上用公式字段展示Email Template超链接:
"https://cs57.salesforce.com/00X28000000OJJH?setupid=CommunicationTemplatesEmail"
就需要获取"https://cs57.salesforce.com/",方法如下:

HYPERLINK(LEFT($Api.Partner_Server_URL_260, FIND( '/services', $Api.Partner_Server_URL_260))+ Email_Template_ID__c +'?setupid=CommunicationTemplatesEmail', Email_Template_Name__c )



85、【Semi-Joins sub selects with IN】:Semi-Joins with IN and Anti-Joins with NOT IN

SELECT Id, Name From School__c WHERE Network_Code__c IN (SELECT Name From School_Networks__c WHERE Location__c = 'a015D000003SVcz')

上述查询将会出现异常Exceptionsemi join sub selects can only query id fields, cannot use: 'name'
原因是:SOQL语法只支持The subquery can filter by ID (primary key) or reference (foreign key) fields.

84、【系统空指针相关测试】:

测试1:

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第21张图片

测试2:

List accs;
for(Account acc : accs) {
    System.debug('check point1');
}
System.debug('check point2');

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第22张图片
总结:测试1Line6虽然查出来结果为空,但是赋值给accs时,其实是一个空数组类型,这和空指针是截然不同的两个概念,而测试2Line1声明变量时未实例化,则默认为空。

83、【字段历史跟踪Field History Track父对象子查询格式】:
Example:比如我们想查询Account及相关的AccountHistory,格式如下:

SELECT Id, Name,Recordtype.Name, Account_Status__c, (SELECT Field, OldValue, NewValue, CreatedById, CreatedDate FROM Account.Histories WHERE Field = 'RecordType') FROM Account

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第23张图片
82、【Process Builder创建定时任务】:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第24张图片
81、【启用多币种Multi-Currency和启用高级货币管理】:
17年在实施Panasonic项目时,启用多币种需要联系客服,那么到今年18年,在准备Sales Cloud Consultant Exam时,发现现在可以自助启用。
那么首先需要在Company Information里面启用多货币,之后你所有相关记录都会默认成组织默认币种,当然你也可以维护静态货币转化率,随后你可以直接启用高级货币管理来维护Dated Currency Rate。
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第25张图片

80、【如何控制Product共享规则?】:
我们查看Sharing Settings,会发现产品相关的对象中只能设置Price Book的记录共享,包含(No Access/Use/View Only),那么产品绑定在Price Book上就能实现RLS了 - 该方法适用于Classic,对于LEX,Price Book画面的Sharing按钮被移除了。

79、【初识Opportunity Stage - 如何为Opportunity Stage赋初始值?】:
Stage是一个比较特殊的字段,即:
记录类型里面没有Stage字段,另外创建记录类型前需先创建Sales Processes
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第26张图片
那么要实现赋初始值不能使用Tirgger(before insert),而需要通过覆盖New按钮(在自定义page里面执行action方法跳链接)或者自定义Page实现:

public class StagePickListController {
    private static String DEFAULT_STAGENAME = 'S0 - Pre-Opportunity';
    private final Opportunity opp;
    public List stages {get; set;}

    public StagePickListController(ApexPages.StandardController stdController) {
        this.opp = (Opportunity)stdController.getRecord();
        initFields();
    }

    private void initFields(){
        this.opp.StageName = DEFAULT_STAGENAME;
        initStages();
    }

    private void initStages(){
        stages = new List();
        // it is better to not hard-code picklist values
        stages.add(new SelectOption('S0 - Pre-Opportunity','S0 - Pre-Opportunity'));
        stages.add(new SelectOption('Qualified','Qualified'));
        stages.add(new SelectOption('Closed Lost','Closed Lost'));
    }
}

   
      
    
        
            
            
        
        
        
            
            
            
            
                
            
            
        
        


78、【Data Loader导入时间问题解决方案】:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第27张图片
77、【使用getValue()处理System.SelectOption数据结构】:

(System.SelectOption[value="00X0I000001qK51UAE", label="Bonus 10% CN", disabled="false"], 
System.SelectOption[value="00X0I000001qK4mUAE", label="Bonus 10% EN", disabled="false"], 
System.SelectOption[value="00X0I000001qK56UAE", label="Bonus 20% CN", disabled="false"], 
System.SelectOption[value="00X0I000001qK4rUAE", label="Bonus 20% EN", disabled="false"], 
System.SelectOption[value="00X0I0000019TWnUAM", label="PDF Bonus Policy for IB IC CN", disabled="false"], 
System.SelectOption[value="00X0I0000019TWsUAM", label="PDF Bonus Policy for IB Imperial CN", disabled="false"], 
System.SelectOption[value="00X0I0000019TWxUAM", label="PDF Bonus Policy for IB Welcome CN", disabled="false"], 
System.SelectOption[value="00X0I0000019TX2UAM", label="PDF Bonus Policy for Loyalty CN", disabled="false"], 
System.SelectOption[value="00X0I0000019TX7UAM", label="PDF Multibank Advantages CN", disabled="false"], 
System.SelectOption[value="00X0I0000019TXCUA2", label="PDF Relatives and friends bonus", disabled="false"])
emailTemplate = emailTemplateOptions[0].getValue();

76、【List的set和get & Object的get】:Sobejct可以做DML操作,Object不行

List originalParents = Database.query(queryStr);
System.debug('originalParents: ' + originalParents);

for(Sobject obj : originalParents) {
    for(String s : allWritableFields) {
        if(!exceptionSet.contains(s)) {
            if(obj.get(s) != parentId2ChildMap.get(obj.id).get(s)) 
                obj.put(s, parentId2ChildMap.get(obj.id).get(s));

            if('version_no__c'.equals(s))
                obj.put(s, String.valueOf(Integer.valueOf(obj.get(s)) + 1));// 父级版本号+1
        }
    }
}


75、【Dynamic SOQL特别注意事项】:
a、Dynamic SOQL with an IN Clause and map.KeySet()

MAP os = new MAP([Select ID from Opportunity]);
SET keys = os.keyset();// 此处必须转一下,不能直接在soql里面使用
String s = 'SELECT  RecordTypeId, COUNT(Id) os FROM Opportunity Where Id IN ';
s+=':keys GROUP BY RecordTypeId';
AggregateResult[] ars = Database.query(s);
System.debug(ars[0].get('RecordTypeId'));

b、实战Sample1

Set recordIdSet = new Set();
switch on sobjectType {
	when 'lead'    {recordIdSet = leadIdSet;}
	when 'contact' {recordIdSet = contIdSet;}
}

String queryStr = 'select id, email from ' + sobjectType + ' where id in :recordIdSet';
List sobjList = Database.query(queryStr);

c、实战Sample2

Boolean isConfig = 'config__c'.equals(sobj.toLowerCase());
String queryStr = (isConfig ? 'SELECT Value__c n' : 'SELECT Name n') + ' FROM ' + sobj + ' WHERE Id = \'' + recId + '\' GROUP BY ' + (isConfig ? 'Value__c' : 'Name');

// Note: 部分時候會出現使用'Id = '\' + recId + '\'過濾未生效,需要變更成:'Id = :recId';

NOTEdynamic SOQL cannot use bind variable fields in the query string. It will yield 'variable does not exist' error. However, it can be used in regular assignment statements.

74、【Organization-Wide Email Addresses】:
在Email Service使用该ID时,sandbox和production环境的ID是一样的。

73、【记录去重最佳实践】:
创建一个Unique字段,使用Workflow以特定维度更新该值,如:待售的房子不能重复,那么如果改房子所在的小区+单元+地理位置+房号就能断定是否为重复记录。

72、【判断字符串是否以特定值结尾】:

field.toLowerCase().endsWith('id');


71、【判断字符串是否为Salesforce ID】:Salesforce sObject Id Validation in Apex

public static Boolean isValid(String stringValue, Schema.SObjectType sObjectType) {
    Id sObjectId;
    if(isId(stringValue)) sObjectId = (Id)stringValue;
    return isValid(sObjectId, sObjectType);
}
 
public static Boolean isValid(Id sObjectId, Schema.SObjectType sObjectType) {
    return !(sObjectId==null || sObjectId.getSObjectType()!=sObjectType);
}
 
public static Boolean isId(String stringValue) {
    return stringValue InstanceOf Id;
}

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第28张图片

70、【在SOQL中使用toLabel限制】:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第29张图片

69、【在SOQL中使用Alias】:Using Aliases with GROUP BY | field Field_Name __c cannot be grouped in a Query call

public static String getConvertedNameById(Id recId) {
	String sobj = String.valueOf(recId.getsobjecttype());
	Boolean isConfig = 'config__c'.equals(sobj.toLowerCase());

	String queryStr = (isConfig ? 'SELECT Value__c n' : 'SELECT Name n') + ' FROM ' + sobj + ' WHERE Id = \'' + recId + '\' GROUP BY ' + (isConfig ? 'Value__c' : 'Name');
	System.debug('queryStr: ' + queryStr);
	return String.valueOf(Database.query(queryStr)[0].get('n'));
}

优化:

// 根据Id获取相关Name值 - 用于字段历史跟踪转义
// 注意:由于Listing Name为自动编号,所以不可Group
public static String getConvertedNameById(Id recId) {
    String sobj = String.valueOf(recId.getsobjecttype());
    Boolean isConfig = 'config__c'.equals(sobj.toLowerCase());
    Boolean isListing = 'listing__c'.equals(sobj.toLowerCase());

    String queryStr = (isConfig ? 'SELECT Value__c n' : (isListing ? 'SELECT Name_as__c n' : 'SELECT Name n')) + ' FROM ' + sobj + ' WHERE Id = \'' + recId + '\' GROUP BY ' + (isConfig ? 'Value__c' : (isListing ? 'Name_as__c' : 'Name'));
    System.debug('queryStr: ' + queryStr);
    return String.valueOf(Database.query(queryStr)[0].get('n'));
}

是否可Group查询参考:

Schema.DescribeFieldResult grp = CustomObject__c.CustomField__c.getDescribe();
system.debug('Can CustomField__c be groupable?'+grp.groupable);

68、【将字符串转化成ID原始数据类型】:

Id myId = Id.valueOf('001xa000003DIlo');

67、【History对象字段 - ParentId】:
我们知道标准对象和自定义对象都有相应的History对象用于存储字段历史跟踪,那么在做动态查询时是有差异的。
Standard Object:为SobjectId;
Custom Object:  为ParentId;

String suffix = 'History';

Id recId = (Id)parmmap.get('recordid');
String sobj = String.valueOf(recId.getsobjecttype());//根据记录id获取对象api name
Boolean isCustomObj = sobj.contains('__c');

System.debug('recId\'s sobject type is: ' + sobj);

String sobjectHistory = (isCustomObj ? sobj.left(sobj.length() - 1) : sobj) + suffix;
String parentType = isCustomObj ? 'ParentId' : (sobj + 'Id');

System.debug('sobjectHistory: ' + sobjectHistory);
System.debug('parentType: ' + parentType);

List histories = Database.query('SELECT Id, IsDeleted, ' + parentType + ', CreatedById, CreatedDate, Field, OldValue, NewValue FROM ' + sobjectHistory + ' WHERE ' + parentType + ' = \'' + recId + '\' ORDER BY CreatedDate');

System.debug('histories: ' + histories);


66、【在Lead上显示Convert按钮】:
当我们使用Lead时,明明将Convert按钮添加到了页面布局,但是用户一直看不到转化按钮,这时需要在Profile里面启用Covert Leads,如下图:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第30张图片
65、【如何让仅仅执行js客户端事件,不执行服务端方法?】:eg. οnclick="jsFunction();return false;"


64、【几种数据导入工具比较】:
Data Loader:使用API Name映射,仅支持CSV文件,External ID类型字段才能关系匹配;
Import Wizard:使用Label匹配,仅支持CSV文件,空控制是否触发workflow和process builder;
Salesforce Inspector:使用API Name映射,支持Excel/CSV文件,支持External ID匹配关系触发Trigger等自动化流程。
结论
使用
210数据做测试,单独启用Process Builder和单独启用Trigger在插入或更新数据时,都生效。
注意:使用Import Wizard时,注意启用Trigger workflow and process builder才会更新记录。

63、【自定义设置Custom Settings可以像Sobject一样被导入导出】:
a、自定义设置能在Developer Console中被soql查询,也能直接做dml操作;
b、自定义设置能通过Salesforce Inspector导入和导出数据;
c、自定义设置能使用Data Loader处理记录;
d、自定义设置能使用Import Wizard导入记录;

62、【在Visualforce中使用Hierarchy Type Custom Setting】:HIERARCHY CUSTOM SETTINGS

{!$Setup.CustomSettingName__c.CustomFieldName__c}

61、【Inspecting Code Coverage】:
Salesforce提供了两种查询方式检索Unit Test覆盖情况:ApexCodeCoverageAggregate/ApexCodeCoverage,使用该对象查询需要启用Use Tooling API,查询模板如下:

SELECT ApexClassOrTrigger.Name, NumLinesCovered, NumLinesUncovered 
FROM ApexCodeCoverageAggregate 
WHERE ApexClassOrTrigger.Name = 'TaskUtil'
SELECT ApexTestClass.Name,TestMethodName,NumLinesCovered,NumLinesUncovered 
FROM ApexCodeCoverage 
WHERE ApexClassOrTrigger.Name = 'TaskUtil'

【Sample】:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第31张图片
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第32张图片
60、【格林威治时间GMT问题】:
最近在做CIL(Custom Interaction Log)时发现如下问题:将System.Now()/DateTime.Now()赋值给时,时间不一致,相差8h
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第33张图片
Solution: 使用format()转一下时间后,再赋值给Text文本即可解决。
Exp: 'Last save at ' + DateTime.now().format();
总结:用纯文本接收时间就会按照格林威治时间显示,需要使用format()转化,才会显示Local时间


59、【为Salesforce启用多币种Multi-Currency功能】:Enable Multi-Currency using the Legacy method for my organization
a、对于DEV Edition可以直接在Company Information中启用,对于sandbox和production需要提交一个Case给Salesforce客户支持,启用过程会花费3个工作日
b、启用后,你在Quick Find里面就能看到Manage Currencies功能,在这你可以新增多个货币,默认的为你启用前Company Information里边的币种。


58、Log日志最大容量为250MB,删除后才能继续打印日志文件。
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第34张图片


57、【Custom Settings】:如下自定义设置API Name为ScheduleDate__c,则我们可以使用obj.getAll()方法获取key为Name字段,value为ScheduleDate__c类型的map,注意Sobject无getAll()方法。Custom Settings Methods
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第35张图片
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第36张图片


56、当启用个人客户后,创建一条account就copy了份account给contact,其中不管两边哪一边有字段值变动,两边都会同步。Exp:contact有字段isActive,如果更新这个字段为true,那么account记录上的该字段跟着变为true。
启发:更新Account可能会触发Contact的Trigger,反之亦然。


55、【使用Trigger.oldMap()过滤进入update/delete trigger的条件】:Trigger Context Variables | Salesforce检查某字段前后是否被更改的方法 - Trigger & Validation Sample
在使用Trigger.New和Trigger.Old相关方法时,最好强转下类型:
Trigger.New      => (List)Trigger.New
Trigger.Old       => (List)Trigger.Old
Trigger.oldMap => (Map)Trigger.oldMap
...
否在将会出现下图错误:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第37张图片


54、15位Id和18位Id能直接判断,不需要使用String.left(15)来截取出相同的长度来比较了,测试如下:
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第38张图片
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第39张图片


53、【为Custom Setting启用List Type】:Setup -> Schema Settings
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第40张图片


52、【在Apex中使用合并统计查询-AggregateResult】:官方文档 | blog 参考


51、【如何在Js/Formula中正确使用Opportunity的HasOpportunityLineItem字段】:在apex中该字段值为Boolean类型

var hasLine = '{!Opportunity.HasOpportunityLineItem}';

alert('{!Opportunity.HasOpportunityLineItem}');0
alert(hasLine);0
alert('hasLine == 0: ' + hasLine == 0);false
alert('hasLine == 1: ' + hasLine == 1);false
alert('hasLine === 0: ' + hasLine === 0);false
alert('hasLine === 1: ' + hasLine === 1);false
alert(hasLine == false);true

50、【获取Label值】

{!$ObjectType.ACC_Return_Order_Product__c.LabelPlural}
{!$ObjectType.ACC_Return_Order_Product__c.Fields.ACC_Product__c.Label}

49、【使用soql查询记录条数】
select count() from DTT__Geographic_Region__c

48、使用Salesforce Inspector导出数据并导入到新环境 - 若存在junction对象的数据导入,则需要mapping关系字段,查询时可通过关系查询查Name。
Example】:导入PriceBookEntry数据到UAT - 先导入PriceBook和PriceBookEntry中需要的Product,后导入PriceBookEntry,之后copy csv数据时,该数据的关系字段查询不要使用Id(Product2Id,Pricebook2Id),而选择查对应的Name,后面导入时,再针对Name做Mapping。
【Note】:导入非标准价格手册的PriceBookEntry前,需要先导入标准的PriceBookEntry,只需Care5个字段:CurrencyIsoCode、Product2Id、Pricebook2Id、UnitPrice、isActive。
Addition】:用inspector导地理区域(自己lookup自己)时,ExternalId是关系的核心,可先将省份单独导入后,再导入其他的数据,如果后续导入报错,再拆开单独导入可以避免意料之外的错误。不要忘录关系字段值
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第41张图片

48、使用Mavensmate同步DEV与UAT环境中简档的字段级别权限:在环境迁移时,部分元数据会存在丢失,导致两环境存在差异,比如简档中档FLS。
Step by Step】:先登陆到DEV环境,利用Mavensmate retrieve Profile和Custom Object(标准对象也在里面),然后勾选某对象如Order的fields和简档明细,点击Update后,在Sublime中打开文件;使用同样档方法将UAT相关文件下载到本地,然后直接复制粘贴简档文件档元数据覆盖UAT即可。

47、如何配置实现超级管理员下次登录salesforce org不需要输入验证码:Profile -> System Administrator -> Login IP Ranges,设置IP Start Address:0.0.0.0,IP End Address:255.255.255.255即可。

46、在apex中启用记录锁来锁定或解锁记录
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第42张图片

select count() from DTT__Geographic_Region__c
select count() from DTT__Geographic_Region__c
select count() from DTT__Geographic_Region__c
select count() from DTT__Geographic_Region__c
select count() from DTT__Geographic_Region__c

45、问题:System Administrator的Profile中Home的Tab Settings显示Default On,在Tab面板和Tab的customize列表中并没有看到Home?- 先禁用profile的enhanced profile user interface,之后在profile的Tab settings中勾上下图1,保存即可。
参见资料:https://help.salesforce.com/articleView?id=Home-Page-Tab-is-Missing-for-Users&language=en_US&type=1
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第43张图片

44、启用或禁用内联编辑 - 有时候我们希望禁用用户默认的行内编辑功能,直接去user interface中将Enable Inline Editing关掉即可。

43、去重Sample - 看似怪诞,实则大有用处:

Map accIDs = new Map();
for(Quote qt: (List)Trigger.New){
    if(!accIDs.containsKey(qt.Quote_To_Account__c))
        accIDs.put(qt.Quote_To_Account__c,'');
}
Map accs = new Map([SELECT id,BillingStreet,BillingCity, BillingState, BillingPostalCode, BillingCountry FROM Account WHERE Id IN : accIDs.keySet()]);

42、System.debug('result: ' + rtMap.get(myRt).equals(qt.RecordTypeId) + '--qt.RecordTypeId: ' + qt.RecordTypeId + '--rtMap.get(myRt): ' + rtMap.get(myRt));Id为18位。

41、Opportunity和Quote为Master-Detail关系,在导入历史数据时,Opportunity和Quote的Owner未同步,事后同步时,不可使用apex方法更新Quote Owner,错误信息为:"Can't change quote owner. Enable quotes without parent opportunities."。

40、如果记录锁定,用户需要更新记录,则必须使用Webservice,比如:报价锁定非系统管理员需自动同步报价时,我们需要在Trigger里面调用Webservice,这时就需要使用到@future异步方式了。

39、Lead Home Page调整经验之谈

a、没有配置方法实现Home页面布局调整,比如将Tools和Reports换个位置;

b、无法用自定义报表链接替换Reports里面的标准报表链接-无法编辑标准报表,无法将自定义报表移至Lead Reports Folder;

c、如果必须实现只能重写标准Lead Tab按钮;

38、Salesforce布局特性:如果相关列表上无任何按钮,那么相关列表面板只有在有记录时才会显示

37、Salesforce获取某个日期所在月份的天数

Date d = System.today();
Integer numberDays = date.daysInMonth(d.Year(), d.Month());
System.debug(numberDays);

36、在为输入元素赋初始值时,我们经常会使用url将参数写入该输入元素的Name属性,我们通常会担心,在Org迁移时,这些Name值是不是会改变,从而增加我们的维护负担?经调研,Vf画面的Name不会随着Org的改变而改变

35、使用apex获取IP地址ApexPages.currentPage().getHeaders().get('X-Salesforce-SIP');

34、封装Map>技巧 + 去重 | 参考资源:How to Send More than 10 E-mails

Map> userCaseMap = new Map>();
List allCaseLoggedToday = new List();
List salesIds = new List();
List salesRep = [SELECT Id , Name , Email , ManagerId 
                       FROM User 
                       WHERE Profile.Name = 'System Administrator'];
for(User u : salesRep) {
  salesIds.add(u.Id);  
}
allCaseLoggedToday = [SELECT Id, CaseNumber,CreatedById, Owner.Name , Account.Name , Contact.Name  
                      FROM Case 
                      WHERE CreatedDate = TODAY AND CreatedById in : salesIds];
for(Case c : allCaseLoggedToday) {
  if(userCaseMap.containsKey(c.CreatedById)) {
    //Fetch the list of case and add the new case in it
    List tempList = userCaseMap.get(c.CreatedById);
    tempList.add(c);
    //Putting the refreshed case list in map
    userCaseMap.put(c.CreatedById , tempList);
  }else {
    //Creating a list of case and outting it in map
    userCaseMap.put(c.CreatedById , new List{c});
  }
}

33、salesforce soql中不能对公式字段使用Group By;

32、Partner User对Quote Tab不可见,Quote无Sharing Rule,它的共享受Opportunity的控制,如果对Partner User的Quote的OLS设置为View且Opportunity页面布局的Quote放出来了,如果共享Opp的Owner没有创建Quote记录,Partner User不可见Quote相关列表,需要创建一条Quote,才可以看见。

31、Apex中List、Map、Set集合总结:
List:有序、可重复;
Map:无序,key重复则value覆盖;
Set:无序,不可重复;即使重复了,取前面的值,如:

Set s = new Set {1,2,3,2,1};
system.debug('s: ' + s);

DEBUG INFO:s: {1,2,3}
Sample:

List accs = [select id, name from account limit 3];
map m = new map();
String lastId = '';
if(accs != null && !accs.isEmpty()) {
    Integer i = 0;
    for(Account a : accs) {
        System.debug('a['+i+'] : ' + a);
        lastId = a.Id;
        m.put(a.Id, a);
        i++;
    }
}
// 验证List有顺序,Map无顺序
System.debug(m);
System.debug(m.get(lastId));
Map m1 = new Map {
    'key1' => 'value1',
    'key2' => 'value2',
    'key1' => 'value2',
    'key2' => 'value3',  
    'key1' => 'value3'
};
System.debug('m1: ' +m1);
System.debug('m1.key1: ' + m1.get('key1'));

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第44张图片

30、创建Task:

任务:
Task task = new Task();
task.Subject = ‘A信息修改申请';
task.status = 'open';
task.priority = 'High';
task.whatId = '0017F00000CfcdsQAB';// 相关项目,这里是供应商Id
task.ownerId = '0057F000000pe6uQAA';// 被分配人,这里是UserId
// 以下两行是设置提醒
task.IsReminderSet = true;
task.ReminderDateTime = System.now();
insert task;

29、根据自定义地理定位数据类型字段API名表示经纬度

现有API名称为:Geographic_Coordinates__c的地理位置坐标字段,要表示经度使用:Geographic_Coordinates__Longitude__s, 要表示纬度使用:Geographic_Coordinates__Latitude__s

Select Id, Address__c, Geographic_Coordinates__Longitude__s, Geographic_Coordinates__Latitude__s From Store__c

28、Live Agent配置:step by step resource

1、先启用live agent - setup -> live agent settings -> enable

2My Settings -> Advanced settings -> Live Agent User(勾选上)

27、使用Database.query()查询数据集合:

filterStr = 'AND Type__c = \'Text\' ';// 注意写成转义字符形式
query = 'SELECT Name, Type__c, Message__c, Msgpic__c, Mediaid__c, VioceRecognition__c, VoiceFormat__c, VoiceSrc__c ' + 
        'FROM BD_CaseDetail__c ' + 
        'WHERE Case__c = :caseId ' + filterStr + 'ORDER BY CreatedDate ASC LIMIT 500';
caseDetailList = Database.query(query);
caseDetailList = [SELECT Name, Type__c, Message__c, Msgpic__c, Mediaid__c, VioceRecognition__c, VoiceFormat__c, VoiceSrc__c
                  FROM BD_CaseDetail__c
                  WHERE Case__c = :caseId AND Type__c = 'Text' ORDER BY CreatedDate ASC LIMIT 500];

26、Apex实例化一个内部类:

public class OuterClass {
    public class InnerClass {
        String name = '';
        Blob body = '';
    }
}
// 实例化内部类
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = new OuterClass.InnerClass();
/*接口类实例化*/
global class SyncInterface {    
    // 封装数据结构 - 返回结果  
    global class SyncResults{  
        global List responses;  
    }  
      
    global class Response{  
        public String TYPE;  
        public String MSG;  
        public String BUSINESS_ID;  
        public String SALESFORCE_ID;  
        public String PROC_TYPE;              
    }  
}

// 实例化接口response list
SyncResults syncResponseList = new SyncResults();  
syncResponseList.responses = new List();

25、限制点击自定义按钮的简档:

/**
    功能说明:判别用户是否为项目比选相关专员
    参数说明:用户Id
    返回值:true/false
    作者:Wilson Xu
    日期:2017-07-26
    **/   
    public static Boolean isValidUser(String userId){
        Set profileSet = new Set{'品牌专员','事件行销专员','物料制作专员','线上媒介专员','线下媒介专员','展览展示专员','子公司企划专员'};
		String profileName = [SELECT Profile.Name FROM User WHERE Id = :userId].Profile.Name;        
        return profileSet.contains(profileName);
    }
// 验证该用户是否为专员简档用户
    	if(!isValidUser(UserInfo.getUserId())){
            result.code = '1';
            result.msg = '只有相关专员才能发起定向谈判!'; 
            return JSON.serialize(result);
        }

24、使用apex处理日期:

a. DateTime in Apex

DateTime nowTime = System.now();
String now_date = nowTime.format('yyyy-MM-dd');
String now_dateTime = nowTime.format('yyyy-MM-dd HH:mm:ss');
System.debug(now_date+'<------>'+now_dateTime);

debug信息:

14:23:00:002 USER_DEBUG [4]|DEBUG|2017-07-26<------>2017-07-26 14:23:00

b. Date in Apex - String to Date | Date Class valueOf(stringDate)

String strDate1 = '2018-6-6';
String strDate2 = '2018-06-06';
String strDate3 = '2018/6/6';
Date myDate1 = date.valueOf(strDate1);
Date myDate2 = date.valueOf(strDate2);
//Date myDate3 = date.valueOf(strDate3);//System.TypeException: Invalid date: 2018/6/6
System.debug('myDate1: ' + myDate1);
System.debug('myDate2: ' + myDate2);
//System.debug('myDate3: ' + myDate3);

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第45张图片
23、DateTime.getTime()获取时间戳,单位为ms;
22、Decimal.valueOf(ApexPages.currentPage().getParameters().get('st')),将字符串形式转换成Decimal格式
21、ApexPages.currentPage().getUrl().contains('st'),apex查看当前页面url是否包含某个字符串
20、图解批准进程approval process,理解队列,多级审批:http://blog.csdn.net/itsme_web/article/details/73250952
19、pageBlockTable中的column组件设置宽度不生效的解决方案:
a、嵌套关系1: form>pageBlock>pageBlockTable>column,可以直接为column增加width的style或者styleClass,需要的话pageBlockTable设置个width:100%;
b、嵌套关系2: form>pageBlock>pageBlockSection>pageBlockTable>column,这个时候添加style和styleClass是不生效的,此时只需要在pageBlockTable中设置columnsWidth="width1,width2...";

18、创建visualforce的4种方法:
a、导航法 - setup->developer->visualforce page;
b、developer console;
c、IDE - eg. sublime text / eclipes - 安装eclipes并配置forceIDE插件视频 - https://www.youtube.com/watch?v=ufe62nGecMg
d、打开developer mode(两种方式:1. 在User里面-Edit-Development Mode;2. 在My Setting-Personal-Advanced User Details-Edit-Development Mode)后,通过url方式创建
Salesforce中常用技能总结(纯粹干货,深度积累)图解_第46张图片

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第47张图片

17、apex:page组件与apex:sectionHeader组件属性的相互影响:
apex:page组件中比较少用到的属性:

如果page中用到了setup="true"属性,那么sectionHeader中就不能显示对象图标。

16、在Js中两种方法获取组件id
a、{!$Component.idName} - eg.  var el1 = document.getElementById('{!$Component.name}').value;// 该方法不需要写嵌套的id,若要指明则:$Component.bk.age
b、pg:fm:bk:name - eg. var el2 = document.getElementById('pg:fm:bk:name').value;// 该方法每个组件均需要指明id,并按嵌套关系依次书写,id间以:隔开
当然我们也希望在css中应用id选择器,我们可以:


    
        
        
        
            
            
                
            
            
                
                
            
            
                
            
            
        
    

15、15位Id与18位Id的区别?

C:Q:: what is the difference 18 digit id and 15digit?

Ans: When we create record in the object salesforce will create 18 digit unique to recognize it.

This is a case insensitive

Same 18 digit is represented as 15 digit id as case sensitive

Id 001—9000001—1o2Of in this 15/18 digit id first 3digits indicate Object and last 5 digits indicate record

===========================排序策略调整<最近的在最前面>==========================

1、使用SOQL语句查询时,字符串类型的只能使用‘单引号’,否则报错:Unknown error parsing query;

eg:SELECT Id, Subject, CaseNumber, Status, Priority 

        FROM Case 

        WHERE CaseNumber = '00001036' //注意此处autoNumber类型数字为String类型

使用SOQL其他注意事项:

a、尽量使用Order By,保证查询结果的稳定性;
b、使用LIMIT,防止数据量超过50000而报错;
c、使用Offset偏移时,一定放在该查询语句的最后面,其限制及应用场景见:https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_offset.htm?search_text=offset

2、标准字段的API Name即为该标准字段的Field Name;

eg:Case标准对象的Subject API Name即为 Subject

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第48张图片

3、计算两个日期之间相隔的天数:

eg:TODAY() - DATEVALUE( CreatedDate )

        Implementd__Date__c - DATEVALUE( CreatedDate )

注意:只有当日期为标准字段时,才使用DATEVALUE()来转化

4、在terminal中使用curl方式查看json数据(通常使用workbench>utilities>REST Explorer),通常会用到sessionId,获取方法如下:

String sessionID = UserInfo.getSessionId();
System.debug(sessionID);

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第49张图片

5、完整版REST services demo

@RestResource(urlMapping='/Cases/*')
global with sharing class CaseManager {
    @HttpGet
    global static Case getCaseById() {
        RestRequest req = RestContext.request;
        String caseId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
        Case result = [SELECT CaseNumber, Subject, Status, Origin, Priority
                       FROM Case
                       WHERE Id = :caseId];
        return result;
    }
    /*
HttpGet步骤:
1、创建RestRequest类型的req对象(RestContext.request的返回值类型就是RestRequest)
2、通过req对象的requestURI属性利用字符串检索技术拿到caseId
3、创建Case对象result,并将通过caseId查到的记录赋值给该对象,注意“WHERE Id = :caseId”
4、返回Case对象
*/
    @HttpPost
    global static ID createCase(String subject, String status,
        String origin, String priority) {
        Case thisCase = new Case(
            Subject=subject,
            Status=status,
            Origin=origin,
            Priority=priority);
        insert thisCase;
        return thisCase.Id;
    }
    /*
HttpPost步骤:
1、声明并创建一个Case类型对象thisCase,并为该对象的标准字段赋值
2、将自定义对象插入到Case表中形成一条记录
3、返回一个新纪录的类型为ID的变量Id用于查找新纪录
*/
    @HttpDelete
    global static void deleteCase() {
        RestRequest req = RestContext.request;
        String caseId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
        Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId];
        delete thisCase;
    }
    /*
思路:
要删除某一条记录首先要找到该记录,而方法可以是利用soql语言查找到某一记录的主码,这里是Id(使用rest服务请求获取到uri后从uri中取得的id)
HttpDelete步骤:
1、创建ResrRequest对象req
2、声明caseId,并将rest请求到的uri截取/后的值赋给该变量
3、利用soql语句查到Id = :caseId的那条记录
4、删除该记录
*/
    @HttpPut
    global static ID upsertCase(String id, String subject, String status, String origin, String priority) {
        Case thisCase = new Case(
        Id = id,
            Subject = subject,
            Status = status,
            Origin = origin,
            Priority = priority
        );
        upsert thisCase;
        return thisCase.Id;
    }
    /*
HttpPut步骤:
1、声明并创建一个Case类型对象thisCase,并为该对象定义标准字段赋值
2、将自定义对象插入到Case表中形成一条记录或者更新Id为id的记录
3、返回一个新纪录的类型为ID的变量Id用于查找新纪录
*/
    @HttpPatch
    global static ID updateCaseFields() {
        RestRequest req = RestContext.request;
        String caseId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
        Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId];
        Map params = (Map)JSON.deserializeUntyped(req.requestBody.toString());
        for(String fieldName : params.keySet()) {
            thisCase.put(fieldName, params.get(fieldName));
        }
        update thisCase;
        return thisCase.Id;
    }
    /*
HttpPatch步骤:
1、创建RestRequest类型的req对象(RestContext.request的返回值类型就是RestRequest)
2、通过req对象的requestURI属性利用字符串检索技术拿到caseId
3、创建Case对象,并把按Id查到的Case表记录赋值给该对象
4、将请求获得的requestBody转化成字符串后,反序列化为对象强制转化为Map后赋值给Map变量params
5、遍历对象的key,并在通过id找到的Case对象thisCase中写入key-value
6、更新记录
7、返回记录的id
*/
}
/*
共性:
1、每个对象系统自带一个Id属性,它是系统自动分配的;
2、每一种Http方法均为global static
3、@HttpPut与@HttpPost的区别(upsert,insert)
*/

 

Salesforce中常用技能总结(纯粹干货,深度积累)图解_第50张图片

区别:put vs patch

the same:You can update records with the put or patch methods.

the difference: put can either create new resouce or update the existing resource; patch can update the existing resouce exclusively.

6、Apex中在使用类继承时需要使用到的关键字:extends,super,virtual,override.跟Java继承不同的是,超类必须使用virtual修饰,子类使用override和extends修饰,如果需要重写父类的方法,父类中该方法需要用virtual修饰,子类需要使用override。另外如果子类需要使用超类的域或者方法则需要使用super关键字,注意构造方法的复用不需要用成对的virtual和override关键字修饰超类的构造方法和子类的构造方法。

7、利用公式字段插入图片:IMAGE(path,img_title,height,width)

8、在report中使用The "Power of One" technique来统计不重复的数据

9、在Apex中使用静态资源加载jquery:


    
    
    
    
    
    
    
    
    

10、简单的使用vf标准控制器来展示Contact列表,并实现点击名称跳转详细页面。方法有三种:


	
		
			
  • {!ct.Name}
  • 11、增强lookup查找功能:我们通过lookup进行对父记录Name进行搜索时,通常默认只能使用Name来搜索,有时候比如我们在子记录中想要通过Phone来搜索Account的Name,这个时候可以在setup->enter 'Search Settings' in Quick Search Box中即可增强搜索

    12、将使用html编辑的前端网站放到force.com平台上的方法:将做好的网站,比如shangpinhui/Bootstrap所有文件打包成zip上传到salesforce的Static Resources中,比如拿shangpinhui为例,目录结构为:shangpinhui->images/js/css/index.html,打包上传后命名为ShangpinhuiZip(名字不能一数字开头),之后在Visualforce Pages中编辑如下代码:

    
    

    在action里面通过URLFOR表达式来将页面加载进去。这样就不用考虑修改网站页面资源引用的路径了,注意在Developer Edition里面由于每个账号限制只允许放一个网站绑定一个url,所以要实现多个网站同时上传作为作品展示,可以再做一个列表,分别通过超链接映射到相应的网站上,这样就可以将您的所有作品都绑定在一个页面上分别访问。

    13、在Company Information中可以查看User Licence的使用次数,如下图:

    Salesforce中常用技能总结(纯粹干货,深度积累)图解_第51张图片

    14、recordSetVar与的适用场景比较:
    recordSetVar保存的是SOBject的List,一般会与以及的熟悉standController(即可以传标准对象也可以传自定义对象)连用,常用于输出性质的组件,而对于输入性质的组件,若强行使用需要加[0],这种场景推荐使用标签,来将比较长的api名称用变量存储。

    你可能感兴趣的:(Sales,Cloud,Salesforce,Experience)