function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
HilineHiline 

Revenue Schedule Trigger

Hi, I'm new to apex.  I was wondering if there is way to use an apex trigger to change the way Salesforce amortizes revenue.  For example, if an opportunity product revenue is entered for $6800 for a 12 month period, it will automatically allocate $566.00 for each month, and load the remainder in the 12th month.  However, what I would prefer is that its broken down evenly with decimals as $566.66 for the first 11 months, and the final month gets the extra change ($566.74).

 

Would this be possible to achieve with an apex trigger?  Any assistance on this matter is greatly appreciated.

Best Answer chosen by Admin (Salesforce Developers) 
Pat McQueenPat McQueen

Yes ... You can do this in Apex Code.  Here is somthing that is similar.

 

//  Copyright (c) 2009, Salesforce.com Inc.
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
//    1.    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
//    2.    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
//          documentation and/or other materials provided with the distribution.
//    3.    Neither the name of the salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this
//          software without specific prior written permission. 
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
//  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
//  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
//  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
// This trigger requires two custom fields and a validation rule. 
//
trigger ProductScheduleTriggerBulk on OpportunityLineItem (after insert) {
      //
      // Get prepped by retrieving the base information needed
      //
      Date currentDate;
      Decimal numberPayments;
      Decimal paymentAmount;
      Decimal totalPaid;
      List<OpportunityLineItemSchedule> newScheduleObjects = new List<OpportunityLineItemSchedule>();
           
    // For every OpportunityLineItem record, add its associated pricebook entry
    // to a set so there are no duplicates.
    Set<Id> pbeIds = new Set<Id>();
    for (OpportunityLineItem oli : Trigger.new) 
        pbeIds.add(oli.pricebookentryid);

    // Query the PricebookEntries for their associated info and place the results
    // in a map.
    Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry>(
        [select product2.Auto_Schedule_Revenue__c, product2.Num_Payments__c from pricebookentry 
         where id in :pbeIds]);
         
     // For every OpportunityLineItem record, add its associated oppty
    // to a set so there are no duplicates.
    Set<Id> opptyIds = new Set<Id>();
    for (OpportunityLineItem oli : Trigger.new) 
        opptyIds.add(oli.OpportunityId);

    // Query the Opportunities for their associated info and place the results
    // in a map.
    Map<Id, Opportunity> Opptys = new Map<Id, Opportunity>(
        [select Id, CloseDate from Opportunity 
         where id in :opptyIds]);

  // Iterate through the changes
  for (OpportunityLineItem item : trigger.new) {
         
          if(entries.get(item.pricebookEntryID).product2.Auto_Schedule_Revenue__c == true) {
                  //OK, we have an item that needs to be Auto Scheduled
                  //Calculate the payment amount
                  paymentAmount = item.TotalPrice;
                  numberPayments = (entries.get(item.pricebookEntryId).product2.Num_Payments__c);
                  paymentAmount = paymentAmount.divide(numberPayments,2);
                  //System.debug('* * * * Base Monthly Amount = ' + paymentAmount);
                  // Determine which date to use as the start date.         
                  if (item.ServiceDate == NULL) {
                        currentDate = Opptys.get(item.OpportunityId).CloseDate;
                    }
                    else
                    {
                      currentDate = item.ServiceDate;
                    }
                  totalPaid = 0;
                  // Loop though the payments 
                  for (Integer i = 1;i < numberPayments;i++) {
                        OpportunityLineItemSchedule s = new OpportunityLineItemSchedule();
                        s.Revenue = paymentAmount;
                        s.ScheduleDate = currentDate;
                        s.OpportunityLineItemId = item.id;
                        s.Type = 'Revenue'; 
                        newScheduleObjects.add(s); 
                        totalPaid = totalPaid + paymentAmount;
                        System.debug('********Date ' + currentDate + ' Amount ' + paymentAmount);
                        currentDate = currentDate.addMonths(1); 
                                          }
                  //Now Calulate the last payment!
                  paymentAmount = item.TotalPrice - totalPaid;
                  OpportunityLineItemSchedule s = new OpportunityLineItemSchedule();
                  s.Revenue = paymentAmount;
                  s.ScheduleDate = currentDate;
                  s.OpportunityLineItemId = item.id;
                  s.Type = 'Revenue'; 
                  newScheduleObjects.add(s);               
                  System.debug('********** LAST PAYMENT **********');
                  System.debug('********Date ' + currentDate + ' Amount ' + paymentAmount);
            
        } // End If Auto_Schedule_Revenue
      } // End For OpportunityLineItem

    if (newScheduleObjects.size() > 0) {
      try {
        insert(newScheduleObjects);
         } catch (System.DmlException e) {
          for (Integer ei = 0; ei < e.getNumDml(); ei++) {
              System.debug(e.getDmlMessage(ei));
          } 
          //
          // There should be something here to alert the user what failed!
          //
        } // End Catch
    } // End If greater than 0    
} // End Trigger ProductScheduleTriggerBulk

 

All Answers

Pat McQueenPat McQueen

Yes ... You can do this in Apex Code.  Here is somthing that is similar.

 

//  Copyright (c) 2009, Salesforce.com Inc.
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
//    1.    Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
//    2.    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
//          documentation and/or other materials provided with the distribution.
//    3.    Neither the name of the salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this
//          software without specific prior written permission. 
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
//  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
//  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
//  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
// This trigger requires two custom fields and a validation rule. 
//
trigger ProductScheduleTriggerBulk on OpportunityLineItem (after insert) {
      //
      // Get prepped by retrieving the base information needed
      //
      Date currentDate;
      Decimal numberPayments;
      Decimal paymentAmount;
      Decimal totalPaid;
      List<OpportunityLineItemSchedule> newScheduleObjects = new List<OpportunityLineItemSchedule>();
           
    // For every OpportunityLineItem record, add its associated pricebook entry
    // to a set so there are no duplicates.
    Set<Id> pbeIds = new Set<Id>();
    for (OpportunityLineItem oli : Trigger.new) 
        pbeIds.add(oli.pricebookentryid);

    // Query the PricebookEntries for their associated info and place the results
    // in a map.
    Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry>(
        [select product2.Auto_Schedule_Revenue__c, product2.Num_Payments__c from pricebookentry 
         where id in :pbeIds]);
         
     // For every OpportunityLineItem record, add its associated oppty
    // to a set so there are no duplicates.
    Set<Id> opptyIds = new Set<Id>();
    for (OpportunityLineItem oli : Trigger.new) 
        opptyIds.add(oli.OpportunityId);

    // Query the Opportunities for their associated info and place the results
    // in a map.
    Map<Id, Opportunity> Opptys = new Map<Id, Opportunity>(
        [select Id, CloseDate from Opportunity 
         where id in :opptyIds]);

  // Iterate through the changes
  for (OpportunityLineItem item : trigger.new) {
         
          if(entries.get(item.pricebookEntryID).product2.Auto_Schedule_Revenue__c == true) {
                  //OK, we have an item that needs to be Auto Scheduled
                  //Calculate the payment amount
                  paymentAmount = item.TotalPrice;
                  numberPayments = (entries.get(item.pricebookEntryId).product2.Num_Payments__c);
                  paymentAmount = paymentAmount.divide(numberPayments,2);
                  //System.debug('* * * * Base Monthly Amount = ' + paymentAmount);
                  // Determine which date to use as the start date.         
                  if (item.ServiceDate == NULL) {
                        currentDate = Opptys.get(item.OpportunityId).CloseDate;
                    }
                    else
                    {
                      currentDate = item.ServiceDate;
                    }
                  totalPaid = 0;
                  // Loop though the payments 
                  for (Integer i = 1;i < numberPayments;i++) {
                        OpportunityLineItemSchedule s = new OpportunityLineItemSchedule();
                        s.Revenue = paymentAmount;
                        s.ScheduleDate = currentDate;
                        s.OpportunityLineItemId = item.id;
                        s.Type = 'Revenue'; 
                        newScheduleObjects.add(s); 
                        totalPaid = totalPaid + paymentAmount;
                        System.debug('********Date ' + currentDate + ' Amount ' + paymentAmount);
                        currentDate = currentDate.addMonths(1); 
                                          }
                  //Now Calulate the last payment!
                  paymentAmount = item.TotalPrice - totalPaid;
                  OpportunityLineItemSchedule s = new OpportunityLineItemSchedule();
                  s.Revenue = paymentAmount;
                  s.ScheduleDate = currentDate;
                  s.OpportunityLineItemId = item.id;
                  s.Type = 'Revenue'; 
                  newScheduleObjects.add(s);               
                  System.debug('********** LAST PAYMENT **********');
                  System.debug('********Date ' + currentDate + ' Amount ' + paymentAmount);
            
        } // End If Auto_Schedule_Revenue
      } // End For OpportunityLineItem

    if (newScheduleObjects.size() > 0) {
      try {
        insert(newScheduleObjects);
         } catch (System.DmlException e) {
          for (Integer ei = 0; ei < e.getNumDml(); ei++) {
              System.debug(e.getDmlMessage(ei));
          } 
          //
          // There should be something here to alert the user what failed!
          //
        } // End Catch
    } // End If greater than 0    
} // End Trigger ProductScheduleTriggerBulk

 

This was selected as the best answer
mgodseymgodsey

Thank you @Pat McQueen - this code is super helpful! Do you know if there is any way to have a tirgger that will automatically re-establish a revenue schedule? For example, if the Close Date changes than the number of installments may have to change?