Copying multiple related records into a single text field

Copying multiple related records into a single text field

I was recently working with a customer who had an object with multiple records related to it.

They wished to copy all of the text from all child records and populate a single text box with the combined results.

The related call notes linked to the Task record


This is the first Trigger I have written that loops through multiple records and populates a single field with the combined results.

See how it was done further below:

The Task record has a long ID field which was also populated on each related text entry
The Trigger code below fires on the insert of the new Task record. 

It generates a Map of all GUIDs in the system and then loops through Interaction Notes looking for details to add to the map. 

This then continues as it builds up a String variable that will eventually be the Comments box text.

Illustrating the logic of the Trigger loop

How does it handle changes?

I had to be in some marker to show the difference betweeen the auto-generated text and anything new that the user may type.

I eventually settled on "--------Save your notes above this line--------". 
The Trigger adds a text demarcation to make sure new text is saved
If the Trigger updates and finds this String, then it keeps anything found before it. 

The demarcation line has to be left in place for this to work. Alternatively, this trigger could be set to only fire once upon creation of the task in which case you could edit all of the data.

Example of new text being added to Single Comments Text box

TaskUpdate Trigger

trigger TaskUpdate on Task (before insert, before update) {

if (Trigger.isBefore && (Trigger.isInsert || Trigger.isUpdate)) TaskHelper.processTasks(;


TaskHelper Class

public with sharing class TaskHelper {

public static void processTasks(List<Task> newTasks) {

//Build a list of all users - make ID searchable
List<User> users = [Select ID, FirstName, LastName, Name from User];
Map<String, User> allUsers = new Map<String, User>();
for (User a : users) {
allUsers.put(a.ID, a);
System.debug('Size of allUsers is ' + allUsers.size());

//Build parent and child list of all Interaction Events and Interaction Notes

Map<String, String> allNotes = new Map<String, String>();
List <NVMContactWorld__InteractionEventNote__c> conList = New List<NVMContactWorld__InteractionEventNote__c>();

String combinedNote;
String guid;
String newGuid;
String oldGuid;

for(NVMContactWorld__InteractionEvent__c acc:[Select id,name,CreatedById,NVMContactWorld__TimeStamp__c, NVMContactWorld__CallObjectIdentifier__c,
(SELECT Name, CreatedById, CreatedDate, NVMContactWorld__Detail__c
FROM NVMContactWorld__Interaction_Event_Notes__r)
From NVMContactWorld__InteractionEvent__c
ORDER BY NVMContactWorld__TimeStamp__c ASC NULLS FIRST]){

//System.debug('Outside of loop ' + acc.NVMContactWorld__CallObjectIdentifier__c);

combinedNote = Null;
//guid = Null;

//Loop through child records
for(NVMContactWorld__InteractionEventNote__c con:acc.NVMContactWorld__Interaction_Event_Notes__r){

//Change ID for real name
User createdByName = allUsers.get(con.CreatedById);

newGuid = acc.NVMContactWorld__CallObjectIdentifier__c;

//System.debug('Debug element ' + con);
//We need to merge single records into 1
String noteDetail;

if (con.NVMContactWorld__Detail__c != Null) {
//System.debug('This agent saved no notes');
noteDetail = con.NVMContactWorld__Detail__c;
else {
noteDetail = 'This agent saved no notes';


//Create the actual string
combinedNote = createdByName.Name + ' | on ' + con.CreatedDate + ' | ' + noteDetail + '\r\n';
guid = acc.NVMContactWorld__CallObjectIdentifier__c;

if (allNotes.get(acc.NVMContactWorld__CallObjectIdentifier__c) == Null) {
//System.debug('Map not created - create it');
allNotes.put(guid, combinedNote);
else {
//System.debug('Map already created - update it');
String oldNotes = allNotes.get(acc.NVMContactWorld__CallObjectIdentifier__c);
//System.debug('oldNotes is ' + oldNotes);
allNotes.put(acc.NVMContactWorld__CallObjectIdentifier__c, oldNotes + ' \r\n' + combinedNote);
//System.debug('allNotes is ' + allNotes.values());

} //end outer for

//do we need to touch this record?
Boolean validRecordsFound = false;

System.debug('Starting class');
//Loop through Task records and get GUIDs to query Interaction Events - checking that it is only NVM Tasks

for (Task ss : newTasks) {

if (ss.CallType != Null && ss.CallObject != Null) {

//We found a NVM task - there is work to do
validRecordsFound = True;
//Final call to the built map to populate description

String oldValue = ss.Description;

If (ss.Description == Null) {
ss.Description = '\r\n--------Save your notes above this line--------\r\n' + allNotes.get(ss.CallObject);

else if (oldValue.contains('--------Save your notes above this line--------')) {

Integer thingsToRemove = oldValue.indexOf('--------Save your notes above this line--------');
System.debug('Remove string after position ' + thingsToRemove);
System.debug('Previous call notes found');
ss.Description = oldValue.left(thingsToRemove) + '\r\n--------Save your notes above this line--------\r\n' + allNotes.get(ss.CallObject);
else {
String newValue = oldValue + '\r\n--------Save your notes above this line--------\r\n' + allNotes.get(ss.CallObject);
ss.Description = newValue;

} //end if

else {
//System.debug('No work to do');
} //end for

} //end method
} //end class