I've been working on several workflow and email alerts lately. I recently came across a curious defect with email templates. Honestly, I don't know if it's strictly limited to the templates. The defect involves an object which has standard and custom fields with the same 'name'.
If the object has a Standard field name and a custom field Name__c, an email template will pick up the custom field.
Steps to recreate:
1) Go to Setup->Create and Create a custom object 'obj' with a custom field of Name.
2) Go to Setup->Create and Create a tab to the custom object.
3) Go to the custom object tab and populate the object with Name='AAA', Name__c='BBB'.
4) Go to Setup->Communication Templates and create a test email template with:
Subject: Name should be AAA {!obj__c.Name}
Body: Name 1 should be AAA: {!obj__c.Name}
Name 2 should be BBB: {!obj__c.Name__c}
5) Save the email template.
6) On the template, click 'Send test and verify merge results'
7) Populate the template with a valid contact and your new obj__c object.
8) You will see the defect. The subject will say: Name should be AAA: BBB
Friday, March 18, 2011
Thursday, March 3, 2011
Leads, Campaigns, and Workflow
I have a requirement for a client that wishes to create a Web 2 Lead form and assign it to a campaign.
Once that is done, a workflow shall fire off a series of emails depending on what sort of campaign the lead is tied to. This sounds easy enough to do with Salesforce's out of the box Web2Lead functionality and workflow, but there were some other tweaks we needed, so utilizing our own solution is the only way to go.
- The form -
Utilizing a jQuery modal, the user clicks a link and populates a small web to lead form. When the user hits submit, sever side validation happens in a VF controller, so we can control the UI (messages and labels if a field is required for instance).
- Allowing Portal users to use the web 2 lead form -
Once the form is submitted, it stores the lead in a custom lead object. The custom lead object is needed, since leads cannot be created (owned) by Portal users. When a portal user creates the mirrored lead object, it fires off a trigger calling an @future method to insert the lead and campaign member.
- Tricky workflows with Leads and Campaigns -
Here's the tricky part of the workflow. When the lead is inserted, it doesn't have access to its related Campaign member and hence related Campaign (the Campaign member is a junction object to connect Campaigns to leads or contacts). Our workflow needed to look at the Campaign Name field on the Campaign. The lead doesn't have this when it is inserted, nor if the Campaign member is updated, does it update the lead. Therefore, modifying the campaign member or campaign doesn't fire the workflow rule until the lead is updated next. This is true even if the workflow rule is set to 'Created or did not previously meet the criteria'.
The solution is to create the workflow rule on the Campaign member and not the Lead. When the Campaign Member junction is created, it should have access to both its Campaign and its Lead objects. Thus, when the member is created, we can create a rule like 'Campaign Name equals My Name' and fire off a workflow when the Lead is assigned to the particular campaign.
Hope this helps others.
Once that is done, a workflow shall fire off a series of emails depending on what sort of campaign the lead is tied to. This sounds easy enough to do with Salesforce's out of the box Web2Lead functionality and workflow, but there were some other tweaks we needed, so utilizing our own solution is the only way to go.
- The form -
Utilizing a jQuery modal, the user clicks a link and populates a small web to lead form. When the user hits submit, sever side validation happens in a VF controller, so we can control the UI (messages and labels if a field is required for instance).
- Allowing Portal users to use the web 2 lead form -
Once the form is submitted, it stores the lead in a custom lead object. The custom lead object is needed, since leads cannot be created (owned) by Portal users. When a portal user creates the mirrored lead object, it fires off a trigger calling an @future method to insert the lead and campaign member.
- Tricky workflows with Leads and Campaigns -
Here's the tricky part of the workflow. When the lead is inserted, it doesn't have access to its related Campaign member and hence related Campaign (the Campaign member is a junction object to connect Campaigns to leads or contacts). Our workflow needed to look at the Campaign Name field on the Campaign. The lead doesn't have this when it is inserted, nor if the Campaign member is updated, does it update the lead. Therefore, modifying the campaign member or campaign doesn't fire the workflow rule until the lead is updated next. This is true even if the workflow rule is set to 'Created or did not previously meet the criteria'.
The solution is to create the workflow rule on the Campaign member and not the Lead. When the Campaign Member junction is created, it should have access to both its Campaign and its Lead objects. Thus, when the member is created, we can create a rule like 'Campaign Name equals My Name' and fire off a workflow when the Lead is assigned to the particular campaign.
Hope this helps others.
Labels:
Apex,
Campaigns,
Leads,
Salesforce.com,
Workflow
Monday, January 3, 2011
Force.com Labs Round Robin Record Assignment - Case Owner issue
There's an existing defect in the Salesforce package for Round Robin Record Assignment. (http://appexchange.salesforce.com/reviews?listingId=a0N3000000178fsEAA). The Force.com lab unmanaged package assigns Cases to an owner/queue when available. The package contains two triggers which assign the case to an owner; caseRoundRobin.trigger and caseOwnerUpdate.trigger.
The two triggers inadvertently fight with eachother. The fighting causes a Case to be re-assigned to it's original owner when it is first reassigned ownership. Say you have a case w/ owner John, and you want to reassign it to owner Mary. In the Salesforce UI, after you save the ownership, it reverts back to John on the first attempt. A second attempt will get you to the right owner, however.
The defect arises in caseOwnerUpdateTrigger. After the code loops through the effected cases in the Trigger, via Trigger.new, it adds the cases to a temp list and then does a SOQL query to retrieve them once more via Id. The second loop does a query against uncommitted data and returns the yet unmodified owner of the Case, which would be John from the example above. The case is then updated w/ the former Owner and appears the Case never changes hands.
The following change to the trigger should clear things up, consolidating everything into one loop, with no 'dirty', or uncommitted reads.
Two notes here: 1) Since the case in the loop is in process of being edited by the Trigger, it is locked and in read-only mode. Therefore, a temp case is created and added to the update list.
2) The update call is made on the list of cases rather than on individual cases in the loop. This is a Salesforce best practice, in order to mitigate SOQL queries and DML.
The two triggers inadvertently fight with eachother. The fighting causes a Case to be re-assigned to it's original owner when it is first reassigned ownership. Say you have a case w/ owner John, and you want to reassign it to owner Mary. In the Salesforce UI, after you save the ownership, it reverts back to John on the first attempt. A second attempt will get you to the right owner, however.
The defect arises in caseOwnerUpdateTrigger. After the code loops through the effected cases in the Trigger, via Trigger.new, it adds the cases to a temp list and then does a SOQL query to retrieve them once more via Id. The second loop does a query against uncommitted data and returns the yet unmodified owner of the Case, which would be John from the example above. The case is then updated w/ the former Owner and appears the Case never changes hands.
The following change to the trigger should clear things up, consolidating everything into one loop, with no 'dirty', or uncommitted reads.
Two notes here: 1) Since the case in the loop is in process of being edited by the Trigger, it is locked and in read-only mode. Therefore, a temp case is created and added to the update list.
2) The update call is made on the list of cases rather than on individual cases in the loop. This is a Salesforce best practice, in order to mitigate SOQL queries and DML.
trigger caseOwnerUpdate on Case (after update) {
ListupdateCS = new List ();
Mapcases = new Map ();
for (Case cs : Trigger.new)
{
if(Trigger.isUpdate) {
System.debug('>>>>> Owner ID: '+cs.ownerId+' Temp Owner ID: '+cs.TempOwnerId__c);
if(cs.TempOwnerId__c <> null && cs.TempOwnerId__c <> '') {
if(cs.OwnerId <> cs.TempOwnerId__c) {
Case temp = new Case();
temp.OwnerId = cs.TempOwnerId__c;
temp.TempOwnerID__c = 'SKIP';
updateCS.add(temp);
}
}
}
}
System.debug('>>>>>Update Cases: '+updateCS);
//
//Update last assignment for Assignment Group in batch
//
if (updateCS.size()>0) {
try {
update updateCS;
} catch (Exception e){
}
}
}
Monday, December 27, 2010
Inheritance and Visualforce page controllers
Salesforce Apex is pretty finicky with inheritance in comparison to Java.
Even though it is Java based, there are many caveats, especially in inheritance and OO related principles.
In Visualforce, you have the option of denoting either a StandardController or Controller, in addition to controller extensions. The extensions may or may not have code in common with the controller. If they do, then you'll want to take advantage of inheritance and creating an abstract controller.
Below is a simple example of a Visualforce page utilizing an abstract controller.
Test.page:
<apex:page controller="Test" extensions="Test1,Test2,Test3">
<apex:form>
<apex:commandButton value="Go" action="{!go}" rerender="out" />
<apex:outputPanel id="out">{!output}</apex:form>
</apex:page>
Test.cls:
Test1.cls:
Test2.cls:
Test3.cls:
FYI, if you're reading through the Advanced developer study guide, 'Test1' is printed after clicking the go command button.
Even though it is Java based, there are many caveats, especially in inheritance and OO related principles.
In Visualforce, you have the option of denoting either a StandardController or Controller, in addition to controller extensions. The extensions may or may not have code in common with the controller. If they do, then you'll want to take advantage of inheritance and creating an abstract controller.
Below is a simple example of a Visualforce page utilizing an abstract controller.
Test.page:
<apex:page controller="Test" extensions="Test1,Test2,Test3">
<apex:form>
<apex:commandButton value="Go" action="{!go}" rerender="out" />
<apex:outputPanel id="out">{!output}</apex:form>
</apex:page>
Test.cls:
public abstract class Test {
public String output {get; set;}
private Test controller = null;
public Test(){}
public Test(Test controller){
this.controller = controller;
}
public virtual void go(){
output = 'Test';
}
}
Test1.cls:
public class Test1 extends Test{
public override void go(){
this.output = 'Test1';
}
//Visual force needs this constructor to perform the controlling logic
public Test1(Test test){ super(test); }
}
Test2.cls:
public class Test2 extends Test{
public override void go(){
this.output = 'Test2';
}
public Test2(Test test){super(test);}
}
Test3.cls:
public class Test3 extends Test{
public override void go(){
this.output = 'Test3';
}
public Test3(Test test){super(test);}
}
FYI, if you're reading through the Advanced developer study guide, 'Test1' is printed after clicking the go command button.
Labels:
Abstract class,
Apex,
Controller,
Inheritance,
Salesforce.com,
Visualforce
Thursday, December 16, 2010
ORA-12519
I created a polling adapter in my BPEL process. Once I threw it on the server, it caused the Oracle XE (10.1.3?) database to crash. After the database crashed, I could not view the BPEL console nor execute any commands on the database through SQL Developer. The error I received was 'ORA-12519'.
What happens is that the XE database by default only allows a small number of connections. BPEL consumes those connections rather quickly. If you encounter this problem, restart the XE database:
What happens is that the XE database by default only allows a small number of connections. BPEL consumes those connections rather quickly. If you encounter this problem, restart the XE database:
- Perform the following SQL as SYS user: ALTER SYSTEM SET PROCESSES=150 SCOPE=SPFILE
- Restart the database
- Restart BPEL / SOA
Wednesday, December 15, 2010
BPEL and TopLink - Method Accessors have not been selected
This error occurs when adding a DbAdapter in BPEL, having a master-detail or 1 to many relationship. There's more information on this defect and a solution at this link: http://iadviseblog.wordpress.com/category/toplink/. Search for 'Method accessors'.
BPEL - Importing schemas for Polling orchestration
By default, BPEL orchestrations are started with a client partner link with two activities, one for input and one for output. When creating a polling orchestration, or an orchestration that polls from a database rather then waits for an http request, the first task is to remove the partner link and the two activities. Unfortunately, this renders the client WSDL useless.
Without the client WSDL, there's no other way to import additional schemas into the BPEL orchestration. Or is there? When you create the polling activity in BPEL, it generates an XSD and a WSDL file for the Polling partner link. When you need to add additional schemas to your BPEL process, then add the following into your Polling WSDL. (If the types element already exists, then you can add your schema import there).

After saving the WSDL, be sure to add the declaration of the schema and namespace to your bpel source. Then you should be able to reference the schema in your orchestration.
Without the client WSDL, there's no other way to import additional schemas into the BPEL orchestration. Or is there? When you create the polling activity in BPEL, it generates an XSD and a WSDL file for the Polling partner link. When you need to add additional schemas to your BPEL process, then add the following into your Polling WSDL. (If the types element already exists, then you can add your schema import there).

After saving the WSDL, be sure to add the declaration of the schema and namespace to your bpel source. Then you should be able to reference the schema in your orchestration.
Labels:
BPEL integration,
Database polling,
Integration,
Oracle,
WSDL,
XSD
Subscribe to:
Posts (Atom)