merry you get the exception, because you run out of opportunities that meet the criteria StageName = '09-Win' and Type 'Reseller / Partner Registration'. That is because your code does not necessarily clones the opportunity the user updates, but the first opportunity that is returned by your query
Opportunity opp = [select Id, closedate, account.name, account.id from Opportunity where StageName = '09-Win' and type = 'Reseller / Partner Registration' LIMIT 1];
I strongly recommend you read https://force.siddheshkabe.co.in/2012/01/basic-of-writing-apex-trigger.html
Below the code, that archives what you are looking for
trigger partnerOpClone on Opportunity (after update) {
// accounts ids to query
Set<Id> oppAccountIds = new Set<Id>();
// opportunities we have to clone
Opportunity[] oppsToClone = new Opportunity[]{};
// clones to save
Opportunity[] oppsToSave = new Opportunity[]{};
String STAGE_WON = '09-Win';
String TYPE_PARTNER = 'Reseller / Partner Registration';
// bulkified handling
for (Opportunity record:trigger.new)
{
// make sure the opportunity stage has been changed to new
if (record.StageName == STAGE_WON && trigger.oldMap.get(record.Id).StageName != STAGE_WON
&& record.Type == TYPE_PARTNER)
// keep for query
oppAccountIds.add(toCheck.Id);
// keep opps that meet the criteria for further processing
oppsToClone.add(record);
}
// make sure we only continue , if there are any opps we have to cline
if (oppsToClone.size() > 0)
{
// query accounts and store by there name to lookup it up quickly
Map<Id,Account> accountMap = new Map<Id,Account>([
select Id
, Name
from Account
where Id IN: oppAccountIds]);
// clone the opps
for (Opportunity record:oppsToClone)
{
Opportunity theClone = record.clone(false,true);
theClone.Amount = null;
theClone.Name = accountMap.get(record.AccountId).Name + ' Partner';
oppsToSave.add(theClone);
}
insert oppsToSave;
}
}
Here is the solution that I was discussing in the comments. It's very similar in concept to your's except it tries to reduce the number of DML and SOQL statements by grouping the Attachments into batches of 5MB (this will need adjusting, I just chose it to prove the concept).
The code could do with cleaning up a bit, and you'll want to change how the ParentIds are selected/assigned, but hopefully the concept is clear:
List<List<Id>> batches = new List<List<Id>>();
List<Integer> batchSizes = new List<Integer>();
for(Attachment attachment : [SELECT Id, BodyLength from Attachment WHERE ParentId = '5002000000ahX5f'])
{
Boolean batched = false;
for(Integer i = 0; i < batches.size(); i++)
{
Integer batchSize = batchSizes[i];
if(batchSize + attachment.BodyLength < 10000000)
{
batches[i].add(attachment.Id);
batchSizes[i] += attachment.BodyLength;
batched = true;
break;
}
}
if(!batched)
{
batches.add(new List<Id>{attachment.Id});
batchSizes.add(attachment.BodyLength);
}
System.debug('>>>first loop heap=' + Limits.getHeapSize());
}
for(List<Id> batchIds : batches)
{
List<Attachment> attachmentsToInsert = new List<Attachment>();
for(Attachment attachment : [SELECT Name, Body FROM Attachment WHERE Id IN :batchIds])
{
attachmentsToInsert.add(new Attachment(Name = attachment.Name, Body = attachment.Body, ParentId = '5002000000ahX5v'));
}
System.debug('>>> second loop heap=' + Limits.getHeapSize());
insert attachmentsToInsert;
}
The output for an object with 5 Attachments (4.28MB, 1.23MB, 1.24MB, 3.59MB, 4.29MB) is as follows:
>>>first loop heap=1871
>>>first loop heap=1917
>>>first loop heap=1955
>>>first loop heap=1993
>>>first loop heap=2015
>>> second loop heap=4503453
>>> second loop heap=2589001
>>> second loop heap=4489577
>>> second loop heap=3759972
Number of SOQL queries: 5 out of 100
Number of DML statements: 4 out of 150
In this case it has only saved 1 DML, but with a lot of small Attachments or increasing the limit this will save a lot more.
For example, using a limit of 10MB gives you the following:
>>>first loop heap=1871
>>>first loop heap=1901
>>>first loop heap=1939
>>>first loop heap=1961
>>>first loop heap=1983
>>> second loop heap=9549616
>>> second loop heap=5788869
Number of SOQL queries: 3 out of 100
Number of DML statements: 2 out of 150
Best Answer
You can do that using triggers. To Answer your second question, see the explanation below
This is because trigger is executed before the workflow rule, and as a result whenever a new record is created this RecordId will not contain any value.
But when a record is being cloned this field RecordId will contain the SFDC record Id of the original object from where it is being cloned. You will be able to identify if this is new record or cloned record and take appropriate action
Actually both of your questions are related and can be answered with above solution.
Please let me know in case of any queries. If this answer helps you resolving your query, mark this as best answer.