Assume the following
Staging__c // object inserted by SOAP API
Foo__c // target object 1 built from Staging__c
Bar__c // target object 2 built from Staging__c
Assume also that partial successes are allowed; if it is all-or-nothing, then code gets simpler
After insert trigger on Staging__c
Foo__c[] fooInsList = new List<Foo__c>();
Bar__c[] barInsList = new List<Bar__c>();
for (Staging__c s: Trigger.new) {
fooInsList.add(new Foo__c(fld1 = s.fldX, ...); // do the split part 1
barInsList.add(new Bar__c(fldA = s.fldY, ...); // do the split part 2
}
// Our correlated errors map; key is the 0...n index into Trigger.new
// relies on fact that each staging record generates 1 foo and 1 bar record
map<Integer,List<String>> stagingIxToErrorsMap = new map<Integer,List<String>> ();
for (Integer i = 0; i < Trigger.new.size(); i++)
stagingIxToErrorsMap.put(i,new List<String>();
Database.SaveResult[] srList = Database.insert(fooInsList,false); // insert foos, partial success allowed
for (Integer i= 0; i < fooInsList.size(); i++)
if (!srList[i].isSuccess()) // failed? save the error message
stagingIxToErrorsMap.get(i).add(srList[i].getErrors()[0].getMessage());
srList = Database.insert(barInsList,false); // insert bars
for (Integer i= 0; i < barInsList.size(); i++)
if (!srList[i].isSuccess())
stagingIxToErrorsMap.get(i).add(srList[i].getErrors()[0].getMessage());
// Report back to Soap client via addError any rows that had 1+ errors
for (Integer i = 0; i < Trigger.new.size(); i++)
if (stagingIxToErrorsMap.get(i).size() > 0)
Trigger.new[i].addError(String.join(stagingIxToErrorsMap.get(i),'\n'));
// At this point, if Foo[x] succeeded but Bar[x] failed,
// the inserted Foo[x] needs to be rolled back; and vice-versa.
// If both failed, no rollback required
Sobject[] rollbackSobjList = new List<Sobject>();
for (Integer i = 0; i < Trigger.new.size(); i++)
if (stagingIxToErrorsMap.get(i).size() > 0)
rollbackObjList.addAll(new List<Sobject>{fooInsList[i],barInsList[i]});
Database.deleteResult[] drList = Database.delete(rollbackSobjList);
for (Database.DeleteResult dr : drList) {
// do something..you can ignore errors on records that don't exist
// if delete fails on records that were inserted, then you could
// fail the whole batch as this is indicative of a rollback failure
}
I typed this in so some typo-type compile errors may be present
There are other patterns to solve this and others may jump in with their favorites
trigger.newMap.get(stagingRecordId).addError(..)