You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Salesforce Apex触发器出现NullPointerException,寻求解决帮助

问题:修改Service Appointment状态触发NullPointerException错误

问题详情

在Salesforce Classic中手动将Service Appointment(服务预约)状态改为「Complete(完成)」或「Cannot Complete(无法完成)」时,触发System.NullPointerException错误,错误指向FSL.TR001_Service_BeforeUpdate触发器调用的自定义Apex类SendServiceReport。

错误根源分析

从提供的代码中,可能引发NullPointerException的关键点包括:

  1. SOQL单条查询无结果时抛出异常:直接使用单条SOQL查询(如ServiceAppointment service = [SELECT ...];),如果无匹配记录会抛出QueryException,而非返回null,后续的null判断无效。
  2. ContentVersion.Title为空时调用字符串方法:若关联的ContentVersion记录Title字段为空,调用contains()或substring()方法会直接触发NullPointerException。
  3. EmailTemplate查询无结果时抛出异常:同样,单条查询EmailTemplate如果未找到匹配模板,会抛出QueryException而非返回null。

修复方案

1. 安全处理SOQL查询结果

将单条SOQL查询改为列表查询,通过判断列表是否为空来避免异常:

// 替换原ServiceAppointment查询
List<ServiceAppointment> serviceList = [
    SELECT 
        Id, 
        ContactId, 
        Technician_Comments__c, 
        Customer_Comments__c, 
        Work_Performed__c, 
        Contact.Email,
        AppointmentNumber,
        Account.Name,
        AccountId
    FROM ServiceAppointment 
    WHERE Id = :serviceAppointmentId
];
if (serviceList.isEmpty()) return new List<Boolean>{false};
ServiceAppointment service = serviceList[0];

// 替换原EmailTemplate查询
List<EmailTemplate> templateList = [
    SELECT Id, HtmlValue, DeveloperName
    FROM EmailTemplate 
    WHERE DeveloperName = 'Service_Report_Email_Template' 
    LIMIT 1
];
if (templateList.isEmpty()) return new List<Boolean>{false};
EmailTemplate emailTemplate = templateList[0];

2. 处理ContentVersion.Title为空及文件名长度不足的情况

在处理附件逻辑前,先对Title进行空值检查和长度校验:

for (ContentVersion contentVersion : contentVersions) {
    Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment();
    attBody = contentVersion.VersionData;
    String title = contentVersion.Title;
    
    // 处理Title为空的情况
    if (String.isBlank(title)) {
        title = 'Unnamed_File_' + contentVersion.Id;
    }
    
    if(!title.contains('.pdf') && contentVersion.FileType == 'PDF'){
        attachment.setFileName('Service_Report' + title + '.pdf');
        contentType = 'pdf';
    }
    else if(!title.contains('.jpg') && contentVersion.FileType == 'JPG'){
        attachment.setFileName('Image_' + title + '.jpg');
        contentType = 'jpg';
    }
    else if(!title.contains('.png') && contentVersion.FileType == 'PNG'){
        attachment.setFileName('Image_' + title + '.png');
        contentType = 'png';
    }
    else {
        attachment.setFileName(title);
        // 处理文件名长度不足3的情况
        if (title.length() < 3) {
            contentType = contentVersion.FileType.toLowerCase();
        } else {
            contentType = title.substring(title.length() - 3, title.length()).toLowerCase();
        }
    }
    attachment.setContentType('application/' + contentType);
    attachment.setInline(false);
    attachment.setBody(attBody);
    attachedServiceReport.add(attachment);
}

3. 添加邮件发送异常捕获

避免因邮件发送失败导致状态更新回滚:

try {
    Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email});
} catch (Messaging.MessagingException e) {
    System.debug('邮件发送失败: ' + e.getMessage());
    return new List<Boolean>{false};
}

4. 验证触发器触发条件

检查FSL.TR001_Service_BeforeUpdate触发器的逻辑,确认仅当Service Appointment状态变更为目标值且满足必要条件(如存在关联Contact、Email模板存在等)时才调用SendServiceReport类,减少不必要的触发。

修改后的完整Apex类

public with sharing class SendServiceReport {
    @InvocableMethod(label='Send Service Report' description='Send Service Report')
    public static List<Boolean> sendEmail(List<String> serviceAppointmentIds) {
        if (serviceAppointmentIds.isEmpty()) return new List<Boolean>{false};

        String serviceAppointmentId = serviceAppointmentIds[0];
        System.debug('serviceAppointmentId: ' + serviceAppointmentId);
        
        // 安全查询ServiceAppointment
        List<ServiceAppointment> serviceList = [
            SELECT 
                Id, 
                ContactId, 
                Technician_Comments__c, 
                Customer_Comments__c, 
                Work_Performed__c, 
                Contact.Email,
                AppointmentNumber,
                Account.Name,
                AccountId
            FROM ServiceAppointment 
            WHERE Id = :serviceAppointmentId
        ];
        if (serviceList.isEmpty()) return new List<Boolean>{false};
        ServiceAppointment service = serviceList[0];

        // 获取关联Contact
        Contact contact = service.Contact;
        if (contact == null || String.isBlank(contact.Email)) return new List<Boolean>{false};

        // 创建邮件
        Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
        email.setToAddresses(new String[]{contact.Email});
        email.setTargetObjectId(service.ContactId);
        System.debug('service.ContactId: ' + service.ContactId);
        
        // 安全查询EmailTemplate
        List<EmailTemplate> templateList = [
            SELECT Id, HtmlValue, DeveloperName
            FROM EmailTemplate 
            WHERE DeveloperName = 'Service_Report_Email_Template' 
            LIMIT 1
        ];
        if (templateList.isEmpty()) return new List<Boolean>{false};
        EmailTemplate emailTemplate = templateList[0];
        
        email.setTemplateId(emailTemplate.Id);
        System.debug('emailTemplate.Id: ' + emailTemplate.Id);
        email.setWhatId(service.Id);
        System.debug('service.Id: ' + service.Id);
        
        List<Messaging.EmailFileAttachment> attachedServiceReport = new List<Messaging.EmailFileAttachment>();

        // 查询关联的ContentDocumentLink
        List<ContentDocumentLink> contentDocumentLinks = [
            SELECT ContentDocumentId
            FROM ContentDocumentLink
            WHERE LinkedEntityId = :serviceAppointmentId
        ];

        Set<Id> contentDocumentIds = new Set<Id>();
        for (ContentDocumentLink cdl : contentDocumentLinks) {
            contentDocumentIds.add(cdl.ContentDocumentId);
        }

        // 查询ContentVersion
        List<ContentVersion> contentVersions = [
            SELECT VersionData, FileType, Title
            FROM ContentVersion
            WHERE ContentDocumentId IN :contentDocumentIds
        ];
        
        String contentType;
        Blob attBody;
        for (ContentVersion contentVersion : contentVersions) {
            Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment();
            attBody = contentVersion.VersionData;
            String title = contentVersion.Title;
            
            // 处理Title为空的情况
            if (String.isBlank(title)) {
                title = 'Unnamed_File_' + contentVersion.Id;
            }
            
            if(!title.contains('.pdf') && contentVersion.FileType == 'PDF'){
                attachment.setFileName('Service_Report' + title + '.pdf');
                contentType = 'pdf';
            }
            else if(!title.contains('.jpg') && contentVersion.FileType == 'JPG'){
                attachment.setFileName('Image_' + title + '.jpg');
                contentType = 'jpg';
            }
            else if(!title.contains('.png') && contentVersion.FileType == 'PNG'){
                attachment.setFileName('Image_' + title + '.png');
                contentType = 'png';
            }
            else {
                attachment.setFileName(title);
                // 处理文件名长度不足3的情况
                if (title.length() < 3) {
                    contentType = contentVersion.FileType.toLowerCase();
                } else {
                    contentType = title.substring(title.length() - 3, title.length()).toLowerCase();
                }
            }
            attachment.setContentType('application/' + contentType);
            attachment.setInline(false);
            attachment.setBody(attBody);
            attachedServiceReport.add(attachment);
        }

        email.setFileAttachments(attachedServiceReport);

        // 发送邮件并捕获异常
        try {
            Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email});
        } catch (Messaging.MessagingException e) {
            System.debug('邮件发送失败: ' + e.getMessage());
            return new List<Boolean>{false};
        }

        return new List<Boolean>{true};
    }
}

内容的提问来源于stack exchange,提问作者sebastian rodriguez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 10:37:02