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
RyanhRyanh 

Revenue scheduling unit test not working

I'm trying to test a trigger that sets a value in a custom field on the OpportunityLineItem object after it's inserted based on details in the OpportunityLineItemSchedule records added. The trigger's working fine, but for some reason, my unit test isn't.

 

The unit test code is below, but it's failing on the second assertion (at the very bottom). "Contract_duration__c" is set by the trigger to "schedule.size()" where schedule is:

 

List<OpportunityLineItemSchedule> schedule = [select ScheduleDate from OpportunityLineItemSchedule where OpportunityLineItemId = :LI.Id];

 

I expect that the number of OpportunityLineItemSchedule records will be 12 because that's what I've defined on the instantiated Product2 object (NumberofRevenueInstallments=12). But it's actually coming out with zero records. I'm sure the problem is with my assumptions -- any help?

 

Test Method

 

static testMethod void testScheduledProduct() {
Account account = new Account(name='Unit Test Account',BillingState='CA');
insert account;

Opportunity opportunity = new Opportunity(
AccountId=account.Id,
name='Unit Test Opportunity',
CloseDate=date.today(),
StageName='Decision Maker Interested'
);
insert opportunity;

Product2 product = new Product2(
Name='Unit Test Product',
isActive=true,
CanUseRevenueSchedule=true,
// default scheduling settings
RevenueScheduleType='Repeat',
NumberofRevenueInstallments=12,
RevenueInstallmentPeriod='Monthly'
);
insert product;

Pricebook2 pricebook = [SELECT Id, Name FROM Pricebook2 WHERE isStandard=true AND isDeleted=false AND isActive=true];

PricebookEntry pbentry = new PricebookEntry(
Pricebook2Id=pricebook.Id,
Product2Id=product.Id,
isActive=true,
UnitPrice=0,
UseStandardPrice=false
);
insert pbentry;

OpportunityLineItem opp_product = new OpportunityLineItem(
OpportunityId=opportunity.Id,
PriceBookEntryId=pbentry.Id,
Quantity=1,
UnitPrice=200
);
insert opp_product;

// Setup complete
// Test that the Monthly amount and Contract duration were set correctly
opp_product = [SELECT Monthly_amount__c, Contract_duration__c FROM OpportunityLineItem WHERE Id = :opp_product.Id];

System.assert(opp_product.Monthly_amount__c==200);
System.assert(opp_product.Contract_duration__c==12);
}

 

Trigger

 

trigger setMonthlyAmountAndDuration on OpportunityLineItem (after insert, after update) {
if(!SetMonthlyAmountHelper.hasAlreadySetMonthlyAmount()) {
for (OpportunityLineItem LI : trigger.new) {
PricebookEntry pbentry = [select Product2Id from PricebookEntry where Id = :LI.PriceBookEntryId];
Product2 prod = [select CanUseRevenueSchedule, NumberOfRevenueInstallments from Product2 where Id = :pbentry.Product2Id];

if (prod.CanUseRevenueSchedule==true && prod.NumberofRevenueInstallments>0) {
OpportunityLineItem opp_prod = [select Monthly_amount__c, Contract_duration__c from OpportunityLineItem where Id = :LI.Id];
List<OpportunityLineItemSchedule> schedule = [select ScheduleDate from OpportunityLineItemSchedule where OpportunityLineItemId = :LI.Id];

if (schedule.size()==0)
opp_prod.Monthly_amount__c = LI.UnitPrice;
else
opp_prod.Monthly_amount__c = LI.UnitPrice / schedule.size();

opp_prod.Contract_duration__c = schedule.size();

SetMonthlyAmountHelper.setAlreadySetMonthlyAmount();
Update opp_prod;
}
}
}
}

 

 

 

WhyserWhyser

Two things:

 

1) Where do you add OpportunityLineItemSchedule objects in your test code? I don't see it being added in the trigger either, so I'm confused, and I can see why you're getting zero if that's the case.

 

2) One of your query calls is redundant

 

OpportunityLineItem opp_prod = [select Monthly_amount__c, Contract_duration__c from OpportunityLineItem where Id = :LI.Id];

 

You are already working with your OpportunityLineItem LI, why query for it again and store it in another variable? You can save yourself from extra query calls, and just replace all instances of opp_prod with LI.

 

RyanhRyanh

Addressing the first question: when I add the "Opportunity Product" (aka OpportunityLineItem) to the Opportunity in the UI, it creates the schedule records for me. The trigger doesn't have to - that's what the parameters to the Product2 constructor do:

RevenueScheduleType='Repeat',
NumberofRevenueInstallments=12,
RevenueInstallmentPeriod='Monthly'

 

This trigger works great through the UI -- after I add the Opportunity Product, SFDC adds the schedule for me and my trigger sets the "Contract duration" and "Monthly amount" fields on the Opportunity Product record.

 

This gets to where I'm probably making a bad assumption... I assumed that the creation of the OpportunityLineItem through APEX code would create the OpportunityLineItemSchedule records for me as it does in the UI.

 

 

As for the second point, I'm querying SFDC for the object because PRESUMABLY, the object has been updated by the triggers since I created the object -- correct?

 

 

thanks.

WhyserWhyser

Can you do me a favor and tell me what is returned if you add a System.debug statement where I put it in your trigger (bolded). I haven't used scheduling in products before, so I'm not familiar with when these OpportunityLineItemSchedules are created.

 

As for the second point, OpportunityLineItem LI is taking an object from the Trigger.new array, which should contain the latest data in your line item, when were they updated during the time of the trigger?


Ryanh wrote:

 

Trigger

 

trigger setMonthlyAmountAndDuration on OpportunityLineItem (after insert, after update) {
if(!SetMonthlyAmountHelper.hasAlreadySetMonthlyAmount()) {
for (OpportunityLineItem LI : trigger.new) {
PricebookEntry pbentry = [select Product2Id from PricebookEntry where Id = :LI.PriceBookEntryId];
Product2 prod = [select CanUseRevenueSchedule, NumberOfRevenueInstallments from Product2 where Id = :pbentry.Product2Id];

if (prod.CanUseRevenueSchedule==true && prod.NumberofRevenueInstallments>0) {
OpportunityLineItem opp_prod = [select Monthly_amount__c, Contract_duration__c from OpportunityLineItem where Id = :LI.Id];
List<OpportunityLineItemSchedule> schedule = [select ScheduleDate from OpportunityLineItemSchedule where OpportunityLineItemId = :LI.Id];

 

  System.debug( 'Schedule :\n' + schedule );


if (schedule.size()==0)
opp_prod.Monthly_amount__c = LI.UnitPrice;
else
opp_prod.Monthly_amount__c = LI.UnitPrice / schedule.size();

opp_prod.Contract_duration__c = schedule.size();

SetMonthlyAmountHelper.setAlreadySetMonthlyAmount();
Update opp_prod;
}
}
}
}

 

 

 


 

RyanhRyanh

Here's the output:

 

 

(OpportunityLineItemSchedule:{ScheduleDate=2009-02-26 00:00:00, Id=00oR00000008PrJIAU}, OpportunityLineItemSchedule:{ScheduleDate=2009-03-26 00:00:00, Id=00oR00000008PrKIAU}, OpportunityLineItemSchedule:{ScheduleDate=2009-04-26 00:00:00, Id=00oR00000008PrLIAU}, OpportunityLineItemSchedule:{ScheduleDate=2009-05-26 00:00:00, Id=00oR00000008PrMIAU}, OpportunityLineItemSchedule:{ScheduleDate=2009-06-26 00:00:00, Id=00oR00000008PrNIAU}, OpportunityLineItemSchedule:{ScheduleDate=2009-07-26 00:00:00, Id=00oR00000008PrOIAU}, OpportunityLineItemSchedule:{ScheduleDate=2009-08-26 00:00:00, Id=00oR00000008PrPIAU}, OpportunityLineItemSchedule:{ScheduleDate=2009-09-26 00:00:00, Id=00oR00000008PrQIAU}, OpportunityLineItemSchedule:{ScheduleDate=2009-10-26 00:00:00, Id=00oR00000008PrRIAU}, OpportunityLineItemSchedule:{ScheduleDate=2009-11-26 00:00:00, Id=00oR00000008PrSIAU}, ...)

 

 And the result is that 12 OLIS records are created and the OLI Contact_duration__c and Monthly_amount__c fields are updated (12 and 500 respectively).

 

WhyserWhyser

Can you try this? (changes in bold) I'm not convinced that you need to re-query the Opportunity Line Item.

 

Do you have any idea of the order of execution when it comes to OpportunityLineItems vs OpportunityLineItemSchedule? Because I'm not certain of the order of execution.

 

Here's my idea of how it works

 

1) Request to create OpportunityLineItem (OpportunityLineItemSchedule does not exist at this point)

2) OpportunityLineItem(s) gets saved to database (not committed)

2a) Does the OpportunityLineItemSchedule get created and associated to the Opportunity Line Items here??? It must if you're getting results back from the System.debug statement when running your test method

3) After Trigger (setMonthlyAmountAndDuration) runs and process the OpportunityLineItem(s), and updates the OpportunityLineItems again.

4) Governer rules prevent the OpportunityLineItems from being updated indefinitely.

5) Changes are committed to the database

 

My biggest concern is step 2a).

Immediately after the record is saved to the database, the after triggers run. I don't know whether the OpportunityLineItemSchedule objects are created and associated after. If you take a look at Triggers and Order of Excecution , where do you think OpportunityLineItemSchedules are created? I'm completely baffled that your test is coming out with ZERO records for OpportunityLineItemSchedule, yet you are able to display the OpportunityLineItemSchedule list back to me through your tests. How can you get zero objects back when you are able to display the records in that list?????? My intial thought was that they were NOT being created upon OpportunityLineItem creation, so you're not the only one confused now.

 

 


Ryanh wrote:

 

Trigger

 

trigger setMonthlyAmountAndDuration on OpportunityLineItem (after insert, after update) {
if(!SetMonthlyAmountHelper.hasAlreadySetMonthlyAmount()) {
for (OpportunityLineItem LI : trigger.new) {
PricebookEntry pbentry = [select Product2Id from PricebookEntry where Id = :LI.PriceBookEntryId];
Product2 prod = [select CanUseRevenueSchedule, NumberOfRevenueInstallments from Product2 where Id = :pbentry.Product2Id];

if (prod.CanUseRevenueSchedule==true && prod.NumberofRevenueInstallments>0) {
// OpportunityLineItem opp_prod = [select Monthly_amount__c, Contract_duration__c from OpportunityLineItem where Id = :LI.Id];
List<OpportunityLineItemSchedule> schedule = [select ScheduleDate from OpportunityLineItemSchedule where OpportunityLineItemId = :LI.Id];

if (schedule.size()==0)
LI.Monthly_amount__c = LI.UnitPrice;
else
LI.Monthly_amount__c = LI.UnitPrice / schedule.size();

LI.Contract_duration__c = schedule.size();

SetMonthlyAmountHelper.setAlreadySetMonthlyAmount();

 

// See what the Opportunity Line Item is going to be before being updated

System.debug( LI );
Update LI;
}
}
}
}

 

 

 


 

RyanhRyanh

No solution yet, but I think I know what the problem is... At least with my code as it is -- Just adding an OpportunityLineItem will not, by itself, trigger the schedule to be created. I'm guessing that there is some additional Apex code running on the standard interstitial page where you create an Opportunity Product that generates it.

 

Still looking for solutions.

RyanhRyanh

just for posterity, here's the code I'm using to add the OpportunityLineItem (this time not in a unit test):

 

 

Product2 search_product = [select Id from Product2 where ProductCode='Budget_T1_LP_Call_Proxy']; Pricebook2 pricebook = [SELECT Id, Name FROM Pricebook2 WHERE isStandard=true AND isDeleted=false AND isActive=true]; PricebookEntry pbentry=null; try { pbentry = [select Id from PricebookEntry where Pricebook2Id=:pricebook.Id and Product2Id=:search_product.Id and isActive=true and UnitPrice=0 and UseStandardPrice=false]; } catch (System.Queryexception e) { pbentry = new PricebookEntry(Pricebook2Id=pricebook.Id,Product2Id=search_product.Id,isActive=true,UnitPrice=0,UseStandardPrice=false); insert pbentry; } OpportunityLineItem opp_product = new OpportunityLineItem(OpportunityId=io.Opportunity__c,PriceBookEntryId=pbentry.Id,Quantity=1, UnitPrice=io.monthly_budget__c, ServiceDate=io.Monthly_budget_bill_date__c );

 

 

Willem MulderWillem Mulder
Hi Ryanh, 

Very old thread, but we're having the same problem right now. Did you find out how to have Apex automatically create OpportunityLineItemSchedules for you?