0

I'm trying to create a trigger that will update a checkbox field value based on attachment (Files).

Here's what I have so far:

Map<Id, Opportunity> opps = new Map<Id, Opportunity>();
for(ContentDocumentLink record: Trigger.isInsert? Trigger.new: Trigger.old) {
    if(record.LinkedEntityId?.getSObjectType() == Opportunity.sObjectType) {
        opps.put(record.LinkedEntityId, new Opportunity(Id=record.LinkedEntityId,hasAttachment__C=false));
    }
}
if(opps.isEmpty()) {
    for(Opportunity records: opps.values()) {
        records.hasAttachment__c = False;
    }
}
Set<Id> oppsWithAttachments = new Map<Id, AggregateResult>([
    SELECT LinkedEntityId Id
    FROM ContentDocumentLink
    WHERE LinkedEntityId = :opps.keySet()
    GROUP BY LinkedEntityId
]).keySet();
for(Opportunity record: opps.values()) {
    record.hasAttachment__c = True;
}
update opps.values();

The field get tick, if a new attachment is added. If I removed all of the attachment, the checkbox doesn't get untick. Am I missing anything on my code?

Cheers!

3
  • so you are deleting attachment from opportunity and if it is last attachment the checkbox is not getting unchecked
    – User6670
    Dec 16, 2020 at 12:17
  • Correct! The checkbox is not getting uncheck Dec 16, 2020 at 12:41
  • you trigger is running in delete scenario right?
    – User6670
    Dec 16, 2020 at 12:52

2 Answers 2

1

I think you can improve your logic by handling the two events (insert and delete of the content object) separately.

On insert, you want to check if the ContentDocumentLink's LinkedEntity contains the ID of an opportunity (assuming you want this to run only for opportunities). If it does, then you add it to a set of opportunity records that will have the checkbox field set to true.

On delete, you want to check not only if the LinkedEntity contains the ID of an opportunity (again: assuming you want this to run only for opportunities) but also to check if there are any content files attached to it. If the record contains the ID of an opportunity, then you add this ID to a set of IDs that you'll query against the ContentDocumentLink table. You can group the query result by the LinkedEntityId and store it into a Map<Id, Integer>. If the opportunity's ID is not on the map, then it does not have any content files attached to it (zero is impossible because if there are no records, they won't show up in the query).

trigger ContentDocumentLink on ContentDocumentLink on (after insert, after delete) {
    if (Trigger.isInsert) {
        ContentDocumentLinkTriggerHandler.handleInsert(Trigger.new);
    } else if (Trigger.isDelete) {
        ContentDocumentLinkTriggerHandler.handleDelete(Trigger.old);
    }
}
public class ContentDocumentLinkTriggerHandler {
    public void handleInsert(List<ContentDocumentLink> records) {
        
        String opportunityKeyPrefix = '005';
        Map<Id, Opportunity> opportunities = new Map<Id, Opportunity>();

        for (ContentDocumentLink record : records) {
            if (String.valueOf(record.LinkedEntityId).startsWith(opportunityKeyPrefix)) {
                opportunities.put(
                    record.LinkedEntityId,
                    new Opportunity(Id=record.LinkedEntityId, HasAttachments__c=true)
                );
            }
        }

        update opportunities.values();
    }

    public void handleDelete(List<ContentDocumentLink> records) {
        
        String opportunityKeyPrefix = '005';
        Set<Id> opportunitiesIds = new Set<Id>();

        for (ContentDocumentLink record : records) {
            if (String.valueOf(record.LinkedEntityId).startsWith(opportunityKeyPrefix)) {
                opportunitiesIds.add(record.LinkedEntityId);
            }
        }

        List<AggregateResult> attachmentPerOpportunity = [
            SELECT COUNT(Id), LinkedEntityId
            FROM ContentDocumentLink
            WHERE LinkedEntityId IN opportunitiesIds
            GROUP BY LinkedEntityId
        ];

        Set<Id> opportunitiesWithAttachments = new Set<Id>();

        for (AggregateResult ar : attachmentPerOpportunity) {
            opportunitiesWithAttachments.add(Id.valueOf(String.valueOf(ar.get('expr0'))));
        }

        Map<Id, Opportunity> opportunities = new Map<Id, Opportunity>();

        for (ContentDocumentLink record : records) {
            if (!opportunitiesWithAttachments.contains(record.LinkedEntityId)) {
                opportunities.put(
                    record.LinkedEntityId,
                    new Opportunity(Id=record.LinkedEntityId, HasAttachments__c=false)
                );
            }
        }

        update opportunities.values();
    }
}

Note: I did not compile this code, so it might contain some errors, but it should give you the logic to achieve what you want.

2
  • Hi Renato, appreciate your response. I changed the prefix to '006' and it's working now if I added an attachment. But when I remove all of the attachment the checkbox doesn't get uncheck. Dec 16, 2020 at 23:45
  • Maybe you should check if the item retrieved by the query is not on the Trigger.old list. As I said: I did not test the code. Dec 17, 2020 at 13:07
1

It looks to me like you have the important pieces in place:

  • Iterating over child records to generate a default state for the parent record
  • Using the parent Id to gather all child records

I think your mistake is with the last loop in your trigger

for(Opportunity record: opps.values()) {
    record.hasAttachment__c = True;
}

That loop isn't using the information from the aggregated query that you performed just prior to this loop. Whatever opps you have, this loop is setting all of them to hasAttachment__c = true.

I believe what you meant to do was iterate over the oppsWithAttachments set.

for(Id oppId :oppsWithAttachments){
    opps.get(oppId).hasAttachment__c = true;
}

Also, I believe the following code block accomplishes nothing (and can be removed):

if(opps.isEmpty()) {
    // opps is empty, so opps.values() will return an empty list
    //   and this for loop will never run
    for(Opportunity records: opps.values()) {
        records.hasAttachment__c = False;
    }
}
1
  • Hi Derek, appreciate the response. How should I add the hasAttachment = false Dec 16, 2020 at 23:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .