List<Opportunity> listAccountIds=[SELECT stageName, createdDate, accountId FROM Opportunity WHERE AccountId !=null];
You're essentially querying every record in the database. Instead, use an appropriate filter:
Opportunity[] opportunities = [
SELECT StageName, CreatedDate
FROM Opportunity
WHERE AccountId = :Trigger.new
];
This eliminates the following check:
if(acc.id==opp.AccountId){
Similarly, we can apply this logic for three other conditions:
Opportunity[] opportunities = [
SELECT Id
FROM Opportunity
WHERE AccountId = :Trigger.new AND
CreatedDate < LAST_N_DAYS:30 AND
StageName NOT IN ('Closed Won','Closed Lost')
];
The LAST_N_DAYS:30
targets the last 30 days, so using CreatedDate < LAST_N_DAYS:30
covers dates earlier than the last 30 days.
In addition, we know that we're updating to Closed Lost, so we don't need to update records that are already Closed Lost.
We don't need to copy the values to a new list, we can just assign the new values, and save. That brings the final code down to just:
trigger AccountTrigger on Account (after update) {
Opportunity[] opportunities = [
SELECT Id
FROM Opportunity
WHERE AccountId = :Trigger.new AND
CreatedDate < LAST_N_DAYS:30 AND
StageName NOT IN ('Closed Won', Closed Lost')
];
for(Opportunity record: opportunities) {
record.StageName = 'Closed Lost';
}
update opportunities;
}
As far as the initial if statement:
if(Trigger.isAfter && Trigger.isUpdate){
Never write a condition that will always be true, or always be false. If you need it later, add it later.
Finally, consider using a trigger framework. It's considered a best practice in Salesforce to minimize the amount of logic the actual trigger does. This makes it easier to test the trigger and add enhancements in a centralized manner.
I have my own personal preference as to what a trigger framework looks like, but there are many to choose from. Find one that is no more powerful than what you think you'll need. It's easy to fall into a trap of overengineering.