[SalesForce] Execution of AfterInsert caused by: System.FinalException: Record is read-only

I am creating a cross-objects trigger that whenever a new opportunity is opened, the trigger goes to check whether the company has sold the same product to the customer before; if so, the type of the opportunity will be 'Existing', otherwise it will be 'New'.

trigger InOfficeOp on Opportunity (after insert, after update) {

    Set<Id> account_id = new Set<Id>();
    List<Id> opp_id = new List<Id>();
    for (Opportunity opp: Trigger.new){
        if(opp.HasOpportunityLineItem){            
            account_id.add(opp.AccountId);
            opp_id.add(opp.Id);
        }

    }


    Map<Id, List<Asset>> acc_asset_map = new Map<Id, List<Asset>>();
    Map<Id, List<OpportunityLineItem>> opp_prod_map = new Map<Id, List<OpportunityLineItem>>();

    for(Asset ast: [SELECT  Product2Id, AccountId FROM Asset WHERE AccountId IN: account_id]) {
        if(acc_asset_map!=null && acc_asset_map.containsKey(ast.Product2Id)) {
            List<Asset> asset_List = acc_asset_map.get(ast.Product2Id);
            asset_List.add(ast);
            acc_asset_map.put(ast.Product2Id,asset_List);
        }
        else {
            acc_asset_map.put(ast.Product2Id, new List<Asset>{ast});
            }
    }

    for (OpportunityLineItem oli: [SELECT Product2Id, OpportunityId FROM OpportunityLineItem WHERE OpportunityId IN: opp_id]){
        if(opp_prod_map!=null && opp_prod_map.containsKey(oli.Product2Id)){
            List<OpportunityLineItem> oppLineItem_Recd = opp_prod_map.get(oli.Product2Id);
            oppLineItem_Recd.add(oli);
            opp_prod_map.put(oli.Product2Id, oppLineItem_Recd);        
        }
        else {
            opp_prod_map.put(oli.Product2Id, new List<OpportunityLineItem>{oli});
         }
    }
    Id op;
    for(Id prodId: acc_asset_map.keyset()){
        if(opp_prod_map!= null && opp_prod_map.containsKey(prodId)) {
            for(OpportunityLineItem oppLine_Recd:opp_prod_map.get(prodId)) {
                op = oppLine_Recd.OpportunityId;
            }
        }
    }
    for(Opportunity o:Trigger.new){
        if(o.Id == op){
            o.Type = 'Existing_Business';
        }
        else{
            o.Type = 'New_Business';
        }
    }    
}

The above is my trigger code.

And here is my test:

@isTest
private class OppTriggerTest{
    static void PopulateExtensiontest() 
    {
        Date closeDt = Date.Today();
        date myDate = date.today();

        Account a2 = new Account(Name='icrm test acc');
        insert a2;

        Opportunity oppr = new Opportunity(Name='testing DIE 6/4/2017', AccountId =
a2.Id,StageName='Prodpecting', 
CloseDate = Date.Today());
        insert oppr;

        Pricebook2 pb22 = new Pricebook2(Name='testDIE');
        insert pb22;

        Product2 pro2 = new Product2(Name='BXCD', isActive=true);
        insert pro2;

        PricebookEntry pbe2 =new 
PricebookEntry(unitprice=0.01,Product2Id=pro2.Id, Pricebook2Id=Test.getStandardPricebookId(), isActive=true, UseStandardPrice=false);

        insert pbe2;

        OpportunityLineItem OPplineitem2 = new OpportunityLineItem (Quantity=2,
OpportunityId=oppr.Id, UnitPrice=0.01, PriceBookEntryId =pbe2.Id);
        insert OPplineitem2;

        update oppr;
    }
}

And I got the error message after I exceture the test:

System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, InOfficeOp: execution of AfterInsert caused by: System.FinalException: Record is read-only
Trigger.InOfficeOp: line 51, column 1: []

I don't quite understand what does the message mean. Could anyone please help me what does the error mean? Is my test wrong?

Best Answer

If you want to update fields on trigger records, you should use before events. From the Apex Developer Guide on Triggers:

There are two types of triggers:

  • Before triggers are used to update or validate record values before they’re saved to the database.
  • After triggers are used to access field values that are set by the system (such as a record's Id or LastModifiedDate field), and to affect changes in other records, such as logging into an audit table or firing asynchronous events with a queue. The records that fire the after trigger are read-only.

Note that last sentence:

The records that fire the after trigger are read-only.


So that's what's wrong with your trigger. At first glance, anyway. It seems extremely unlikely to perform according to spec.

As far as your test method goes, you need to tell the platform that it should be executed as a test:

static testmethod void myTest1()
{
    // this syntax is my preference
}
@IsTest
static void myTest2()
{
    // this syntax is also valid
}
Related Topic