We have some custom fields on the standard objects, and the value of this field on the Lead standard object was not properly keeping its value through the lead conversion process into a Contact. There is very little documentation on this process (see the convertLead() method and the ConvertLead Operation) so I was forced to sprinkle debug statements around my code and check the debug logs. I'll summarize the details of the ConvertLead process and its order of operations that may be of use to another Apex developer.
When the ConvertLead process is called, either by pressing the "Convert" standard button on the Lead page or by calling the convertLead Apex method in code, the same steps are followed on Salesforce's side. This is how my echo-location view of how it plays out:
Clicking the "Convert" button does this in this order:
- Insert new Account or update existing Account
- Insert the new Contact
- Insert new Opportunity (optional)
- Update references
- All records with fields that pointed to the old Lead are updated to point to the new Contact
- If an Opportunity was created, all references are updated to point to the new Opportunity instead of the new Contact
- Update old Lead
- The isConverted and other conversion-related fields are updated to 'true'
Now, remember that triggers and validation operations occur before and after each of these DML operations. This order of operations was our biggest question mark when confronting our bug, but we now understand it better, if only by a little. By looking at these notes, because all references will point to the optional Opportunity if it is created, it seems that Salesforce deems the Opportunity to be the more important of the two newly created sObjects (Opportunity and Contact). Keep this little detail in mind as you debug your code.
(Just a guess, but I would guess that these all occur under the same database transaction, and if one part fails, the transaction rolls back and an error is displayed to the user.)