2

我被困在编写这个 Salesforce.com 触发器的语法(也许还有逻辑)上。我希望触发器检查主要联系人是否列在商机的 ContactRoles 中。如果列出了主要的,我需要从相应的联系人中查找 LeadSource 并将该值插入到机会的 Lead Source 中。

非常感谢任何提示或提示!

trigger UpdateContactLeadSource on Opportunity (after insert, after update) {

//Declare the Lead Source Variable which will hold the contact's lead source
string leadsource;

// See if there is a primary contact listed on the Opportunity
for (Opportunity o : Trigger.new) {
OpportunityContactRole[] contactRoleArray =
   [select ContactID, isPrimary from OpportunityContactRole where OpportunityId = :o.id ORDER BY isPrimary DESC, createdDate];

    // If the there is a primary contact, then...
    if (contactRoleArray.size() > 0) {

    // Lookup ContactID on the Contacts table to find the lead source
    for (Contact contact : [SELECT LeadSource FROM Contact WHERE Contact.Id = :OpportunityContactRole.ContactId LIMIT 1]) 

    // Store the actual lead source in the leadsource variable
    { Contact.LeadSource = leadsource;}
    update Opportunity.LeadSource = leadsource; }
}
}
4

2 回答 2

1

您的代码中有几个严重的坏事。希望你不会感到被冒犯,这是一个很好的要求,也是一个学习的机会......

  1. after insert在这里没有任何意义。根据定义,如果您刚刚完成插入此机会,则其中还没有任何联系人角色。*
  2. after update没问题。before update会更好,因为您只需填写该值即可免费保存到数据库。
  3. 我认为您可能必须将此逻辑复制到 OpportunityContactRole 上的类似触发器?而不是重复代码 - 使用一些可以从两个地方调用的类?
  4. 我假设您只想LeadSource在机会为空的情况下填写它?
  5. 您在循环中进行了选择,这是一个非常糟糕的做法;)您的触发器不是“批量”,当有人在一个事务中更新 10 个机会(例如通过使用 Data Loader)时它可能会失败,因为我们的限制为最多 20触发器中的查询。
  6. 您不需要ORDER BYLIMIT 1等等 - Salesforce 将保护您并只允许 1 个联系人作为主要联系人。即使您想使用 Data Loader 以此类错误加载它们。
  7. 阅读有关关系如何在 Salesforce 中运作的信息,您似乎会受益匪浅。与普通 SQL 语言相比,“点表示法”和子查询看起来有点奇怪,但您可以使用它们来大大简化您的代码。如果您过去做过一些面向对象的编程,将会有所帮助。这是一些指南,这是官方文档

TL;博士

trigger fillLeadSource on Opportunity (before update) {
    /*  I'm assuming you want to fill LeadSource in only if it was blank. 
        If that's not the case - just delete this first part of code and in the query instead of ":oppsToFill" bind to ":trigger.new" or
        ":trigger.newMap.keyset()".
        (I know they look weird but you can do it, bind objects/collections in queries that expect Ids / collections of Ids)
    */
    Set<Id> oppsToFill = new Set<Id>();
    for(Opportunity o : trigger.new){
        if(o.LeadSource == null) {
            oppsToFill.add(o.Id);
        }
    }

    // Now we'll select all possible contacts wasting only 1 query.
    if(!oppsToFill.isEmpty()){
        List<OpportunityContactRole> roles = [SELECT OpportunityId, Contact.Name, Contact.LeadSource
            FROM OpportunityContactRole
            WHERE isPrimary = true AND Contact.LeadSource != null AND OpportunityId IN :oppsToFill];

        if(!roles.isEmpty()){
            for(OpportunityContactRole ocr : roles){
                Opportunity oppToBeFilled = trigger.newMap.get(ocr.OpportunityId);
                System.debug('Changing lead source on ' + oppToBeFilled.Name + ' from ' + oppToBeFilled.LeadSource + ' to ' + ocr.Contact.LeadSource + ' (thx to ' + ocr.Contact.Name + ' being the Primary Contact).');
                oppToBeFilled.LeadSource = ocr.Contact.LeadSource;
            }
        }
    }
    // That's it. If there was a primary contact with Lead source, data will be copied over.
    // If there was no primary contact or he didn't have the source filled in - tough luck, we did our best.
    // Since it's before update, you get save to database for free.
}

  • 精细打印到#1,用于高级场景;)如果您在插入后有另一个触发器将在某处添加它们,那么您可以在保存后立即拥有联系人角色......但这会非常混乱(取决于哪个触发器首先触发我会说你会得到一个非常有趣的随机行为,祝你调试好运)。如果您在给定对象上确实有多个触发器,其中包含相同的触发关键字,则建议只有 1 个触发器并完全控制动作顺序。有关更多信息,请参阅http://www.embracingthecloud.com/2010/07/08/ASimpleTriggerTemplateForSalesforce.aspx

编辑以回答评论 re#3 中的问题

before您需要在新触发器中使用相似但不相同的代码(在这种情况下,它是或after- 我们需要显式更新 Opportunities并不重要)。这里有点糟糕,因为您要查看的字段不是直接可用的 - 您可以访问 OpportunityId、ContactId 但不能访问 Contact.LeadSource。这样的事情应该可以解决问题:

trigger ContactRoleRollup on OpportunityContactRole(after insert, after update){
    Map<Id,Id> oppToContactMap = new Map<Id, Id>();
    for(OpportunityContactRole ocr : trigger.new){
        if(ocr.isPrimary){
            oppToContactMap.put(ocr.OpportunityId, ocr.ContactId);
        }
    }
    if(!oppToContactMap.isEmpty()){
        List<Opportunity> oppsToUpdate = [SELECT Id FROM Opportunity WHERE LeadSource = null AND Id IN :oppToContactMap.keyset()];
        Map<Id, Contact> contacts = [SELECT Id, LeadSource FROM Contact WHERE LeadSource != null AND Id IN :oppToContactMap.values()];

        for(Opportunity o : oppsToUpdate){
            Id contactId = oppToContactMap.get(o.Id);
            Contact c = contacts.get(contactId);
            if(c != null){
                o.LeadSource = c.LeadSource;
            }
        }
        update oppsToUpdate;
    }
}

这里很有趣,因为此更新将触发我们的旧触发器。如果您离开了我的“如果填写了leadSource,则跳过”应该没问题,但您仍然可能想探索两件事:

  • 在类中使用一些辅助静态标志,将其称为“skipTriggerOnOpps”,您将在 OpportunityContactRoles 上的触发器中设置此标志,并将 Opportunity 触发器的整个代码包装在其中,这样如果我们已经处理了主要源同步,它就不会执行.
  • 从理论上讲,您可以在不更改任何内容的情况下“触摸”机会(在这种情况下将旧触发器视为一种好处,而不是不必要的副作用)。对我来说,它看起来有点太神奇了,但是如果对这里发生的事情进行了很好的评论,它可能会导致更少的代码、更少的逻辑重复、更少的单元测试......只要它是after触发器就可以工作,因此查询联系人角色我们刚刚修改会看到新的值。它必须看起来像那样

    trigger ContactRoleRollup on OpportunityContactRole(after insert, after update){
        Set<Id> oppIds = new Set<Id>();
        for(OpportunityContactRole ocr : trigger.new){
            if(ocr.isPrimary){
                oppIds.add(ocr.OpportunityId);
            }
        }
        if(!oppIds.isEmpty()){
            update [SELECT Id FROM Opportunity WHERE LeadSource = null AND Id IN :oppIds];
            // That's it. Call update directly on returned list without changing anything, let the other trigger worry about the logic
        }
    }
    
于 2012-12-05T19:56:20.927 回答
0

eyecream 的机会触发器最有帮助,并且正在为我解决问题。不过,值得指出的是,不幸的是,SFDC 尚未支持 OpportunityContactRole 触发器的假设。正如这个想法 ( https://success.salesforce.com/ideaview?id=08730000000BrdvAAC ) 所指出的,目前无法触发 OCR。

于 2013-12-18T11:28:53.477 回答