3

I have a custom object record with a start date, an end date, and a amount field. I want to be able to take the difference between the two dates in number of months, divide the amount by the difference and then create related records based on the difference count. To make a basic schedule.

I think I have to do two for loops in my class, however, I've never done two at the same time before so I'm unsure how to get started.

I know that to get the total number of months I can do this:

Integer numOfMonths = start_date.monthsbetween(end_date) 

I know that to create the records I can do something like this:

for(Integer i = 0; i < numOfMonths; i++) {
    insert new Revenue_Schedules__c(
        Amount__c = amount/numOfMonths,
        Date__c = start_date.addmonths(numOfMonths), 
        );      

How do you loop through values in the newMap to get each variable and then loop again to get records to insert?

-=EDIT=-

input example: Revenue record with start_date = 10-1-2016, end_date = 9-1-2017, amount = 1100

output expectation: 12 Revenue Schedule records
* record 1, date = 10-1-2016 amount = 100
* record 2, date = 11-1-2016 amount = 100
* record 3, date = 12-1-2016 amount = 100
ect..

  • 3
    Never perform dml in a loop of any kind. There are very rare exceptions, but this is not one of them. – Adrian Larson Nov 4 '16 at 15:48
  • 1
    So you would add each iteration to a list and then outside the loop insert. I think I need help getting to the list part. – Dan Wooding Nov 4 '16 at 15:51
  • I think you need to elaborate on your expected inputs/outputs. – Adrian Larson Nov 4 '16 at 15:52
7

You should never do DML statements (such as insert) inside loops, this will easily get you off limits. Create a List outside the loop, iterate over your trigger in the first loop, create new records and add them to the list in the second loop, then insert everything outside the loop. Something like this:

List<Revenue_Schedule__c> new_records = new List<Revenue_Schedule__c>();

for (Your_Trigger_sObject__c current : trigger.new) {
    Integer numOfMonths = current.start_date.monthsbetween(current.end_date);
    for (Integer i=0; i < numOfMonths; i++) {
        new_records.add(
            new Revenue_Schedules__c(
                Amount__c = current.amount / numOfMonths,
                Date__c = current.start_date.addmonths(numOfMonths), 
            )
        );
    }
}

insert new_records;

The important is to keep the DML operation outside the loop. The same applies to queries, keep them always out.

  • 1
    +1. I'd also add that you can do a maximum of 150 DML operations during a single transaction, hence why as Mauricio Oliveira correctly says you should upload a collection of sObjects. – Dan Jones Nov 4 '16 at 16:02
  • So I kept thinking that because i could have multiple starting records I would have to loop through those to get the numofmonths and then loop again to get the new_records to insert. But you're saying you can just do it all in one go? – Dan Wooding Nov 4 '16 at 16:10
  • @DanWooding Yes. Just as you can do insert new Revenue_Schedules__c(... you can also add that Revenue_Schedules__c to a List<Revenue_Schedules__c> and insert the List<Revenue_Schedules__c>. – Dan Jones Nov 4 '16 at 16:23
2

This is what I would do if I were you. Make sure to use one trigger per object and use helper methods for your operations/DML.

This is assuming you need to relate your revenue records to the source record.

trigger customObjTrigger on Custom_Obj__c (after insert){

    if(trigger.isInsert){
        if(trigger.isAfter){
            cust_obj.createRevs(trigger.new);
        }
    }
}

Class with Method

public class custom_obj methods{

        public static void createRevs(List<Custom_Obj__c> newList){

            List<RevenueSchedule__c> revs = new List<Revenue_Schedule__c>();

            for(custom_obj__c c : newList){
                Integer months = c.StartDate__c.MonthsBetween(c.EndDate__c);
                for(Integer i = 0; i<months;i++){
                    RevenueSchedule__c rev = new RevenueSchedule__c();
                    rev.Custom_Obj__c = c.id;
                    rev.Amount__c = c.Amount__c/months;
                    rev.StartDate__c = c.StartDate__c.addMonths(i);
                    revs.add(rev);
                }
            }

            if(revs.size() > 0){
                insert revs;
            }

        }

    }
  • While this answer is technically as correct as the other answer, the code is needlessly complex and consumes far more CPU time than the other answer (close to 400% more CPU time, by my estimate). It won't exceed any governor limits, but it'll make bulk data loads a bit more painful to watch. – sfdcfox Nov 4 '16 at 16:28
  • Yeah, you could do away with the first for-loop to build the map and move the integer variable as is seen in the first answer. Good catch sfdcfox (of course), was just putting this together on the fly. – sfdcJameson Nov 4 '16 at 16:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.