The Good, Bad, and Ugly of Table Level Scripts(CRM v6.2 SP1)
Table level scripts are the perfect compliment to client side actions such as create scripts and on change script because it is server side and allows you to run code based on inserting, updating, or deleting a record. My goal was to update “Average Certainty”, “Weighted Average”, and “Total Forecast” on the ‘Company Summary’ screen every time an opportunity is created, updated, or deleted. Once the “Show pipelines for Companies/People” is set to ‘Yes’ the Opportunity tab under company will display the pipeline and calculated fields for all opportunities. These calculations are done by the Dll so performing the calculations and updating the fields in real time required a table level script under opportunity.
The fields above were created on the company summary screen and will hold our calculations that match the values on the opportunity tab. I tried using the detached table level but I faced the same problem that I have faced in the past. The values insert into the DelayedScriptTable, delete after the allotted time but the values on the company summary screen did not seem to update at all. I have also seen bugs where the entries stay in that DelayedScriptTable and never get executed. Sage support has informed me to stay away from Detached Table Level Scripts so I used a regular table level script here.
PostInsertRecord()
function PostInsertRecord()
{
ccRec = eWare.FindRecord("opportunity",WhereClause);
var oppoid = ccRec.oppo_opportunityid;
var compid = ccRec.oppo_primarycompanyid;
var oppF = Values('oppo_forecast');
var oppAC = Values('oppo_certainty');
//SQL BELOW SETS THESE FIELDS WITH THE RECENT VALUE SO THE SELECT STATEMENT IS USING THE MOST RECENT AND REAL TIME DATA
var oppFSQL = "update opportunity set oppo_forecast = '"+oppF+"', oppo_certainty = '"+oppAC+"' where oppo_opportunityid='"+oppoid+"'";
eWare.ExecSql(oppFSQL);
var sql = "SELECT TOP 20 SUM((Oppo_Forecast/Curr_Rate) * 1.000000) AS Total_Forecast, cast(SUM(Oppo_Certainty) as numeric)/count(oppo_primarycompanyid) as Average_Certainty, (SUM((Oppo_Forecast/Curr_Rate) * 1.000000)/count(oppo_primarycompanyid))*(cast(SUM(Oppo_Certainty)as numeric)/count(oppo_primarycompanyid))/100 as Weighted_Average FROM vListOpportunity (nolock) LEFT JOIN Currency ON Oppo_Forecast_CID = Curr_CurrencyID LEFT JOIN Custom_Captions ON Oppo_Stage = Capt_Code WHERE Oppo_PrimaryCompanyId='"+compid+"' and ( Capt_Family = \'Oppo_Stage\' OR Capt_Family IS NULL) GROUP BY Oppo_PrimaryCompanyId ORDER BY 2 DESC";
var QueryObj = eWare.CreateQueryObj(sql);
QueryObj.SelectSql();
var WA = QueryObj.FieldValue('Weighted_Average');
var TF = QueryObj.FieldValue('Total_Forecast');
var AC = QueryObj.FieldValue('Average_Certainty');
var update = "update company set comp_totalforecast = '"+TF+"', comp_weightedaverage = '"+WA+"', comp_averagecertainty = '"+AC+"' from company where comp_companyid ='"+compid+"'";
eWare.ExecSql(update);
}
I use the “WhereClause” to get the opportunity record id just created. I used Values() in order to get the certainty and forecast values I just entered for that opportunity. I have to set the forecast and certainty values ourselves because the Dll does not do the insert command until after the post insert table level script code is run. Since I do a select statement that performs our calculations on all of the opportunities these forecast and certainty values need to be saved into the database before it is run. This is one of the “bad” things about the PostInsertRecord because it requires us to manually update the opportunity table before CRM does it by design. The “ugly” part about the InsertRecord is it runs code from the UpdateRecord function causing undefined for the Values() method. Once the code does the calculation it updates the company table by inserting those calculations into the fields displayed on the company summary page.
UpdateRecord()
function UpdateRecord()
{
var compid = eWare.GetContextInfo('opportunity', 'oppo_primarycompanyid');
var oppoid = eWare.GetContextInfo('opportunity', 'oppo_opportunityid');
var oppF = new String(Values('oppo_forecast'));
var oppAC = new String(Values('oppo_certainty'));
if(oppF!='undefined')
{
//SQL BELOW SETS THESE FIELDS WITH THE RECENT VALUE SO THE SELECT STATEMENT IS USING THE MOST RECENT AND REAL TIME DATA
var oppFSQL = "update opportunity set oppo_forecast = '"+oppF+"', oppo_certainty = '"+oppAC+"' where oppo_opportunityid='"+oppoid+"'";
eWare.ExecSql(oppFSQL);
var sql = "SELECT TOP 20 SUM((Oppo_Forecast/Curr_Rate) * 1.000000) AS Total_Forecast, cast(SUM(Oppo_Certainty) as numeric)/count(oppo_primarycompanyid) as Average_Certainty, (SUM((Oppo_Forecast/Curr_Rate) * 1.000000)/count(oppo_primarycompanyid))*(cast(SUM(Oppo_Certainty)as numeric)/count(oppo_primarycompanyid))/100 as Weighted_Average FROM vListOpportunity (nolock) LEFT JOIN Currency ON Oppo_Forecast_CID = Curr_CurrencyID LEFT JOIN Custom_Captions ON Oppo_Stage = Capt_Code WHERE Oppo_PrimaryCompanyId='"+compid+"' and ( Capt_Family = \'Oppo_Stage\' OR Capt_Family IS NULL) GROUP BY Oppo_PrimaryCompanyId ORDER BY 2 DESC";
var QueryObj = eWare.CreateQueryObj(sql);
QueryObj.SelectSql();
var WA = QueryObj.FieldValue('Weighted_Average');
var TF = QueryObj.FieldValue('Total_Forecast');
var AC = QueryObj.FieldValue('Average_Certainty');
var update = "update company set comp_totalforecast = '"+TF+"', comp_weightedaverage = '"+WA+"', comp_averagecertainty = '"+AC+"' from company where comp_companyid ='"+compid+"'";
eWare.ExecSql(update);
}
}
I need to use an “If Statement” on the forecast or certainty value because of the “ugly” part of table level scripts mentioned above. It will only run the code below the “if statement” if it is a true update on the record. Essentially the same code in the InsertRecord is run in the UpdateRecord but I use GetContextInfo in order to get the opportunity and company id values. It would be nice if they had a post update function that ran the code after the update statement because when looking at the SQL logs I discovered that the code runs before the values get updated in the opportunity table.
DeleteRecord()
function DeleteRecord()
{
//WE HAVE TO ZERO OUT THE VALUES AND MINUS FROM THE COUNT IN THE SQL STATEMENT BECAUSE IT NEEDS TO DO THE UPDATES ON THE CALCULATION BEFORE THIS RECORD GETS DELETED
var compid = eWare.GetContextInfo('opportunity', 'oppo_primarycompanyid');
var oppoid = eWare.GetContextInfo('opportunity', 'oppo_opportunityid');
var oppF = 0;
var oppAC = 0;
//SQL BELOW SETS THESE FIELDS WITH THE RECENT VALUE SO THE SELECT STATEMENT IS USING THE MOST RECENT AND REAL TIME DATA
var oppFSQL = "update opportunity set oppo_forecast = '"+oppF+"', oppo_certainty = '"+oppAC+"' where oppo_opportunityid='"+oppoid+"'";
eWare.ExecSql(oppFSQL);
var sql = "SELECT TOP 20 SUM((Oppo_Forecast/Curr_Rate) * 1.000000) AS Total_Forecast, cast(SUM(Oppo_Certainty) as numeric)/(count(oppo_primarycompanyid)-1) as Average_Certainty, (SUM((Oppo_Forecast/Curr_Rate) * 1.000000)/(count(oppo_primarycompanyid)-1))*(cast(SUM(Oppo_Certainty)as numeric)/(count(oppo_primarycompanyid)-1))/100 as Weighted_Average FROM vListOpportunity (nolock) LEFT JOIN Currency ON Oppo_Forecast_CID = Curr_CurrencyID LEFT JOIN Custom_Captions ON Oppo_Stage = Capt_Code WHERE Oppo_PrimaryCompanyId='"+compid+"' and ( Capt_Family = \'Oppo_Stage\' OR Capt_Family IS NULL) GROUP BY Oppo_PrimaryCompanyId ORDER BY 2 DESC";
var QueryObj = eWare.CreateQueryObj(sql);
QueryObj.SelectSql();
var WA = QueryObj.FieldValue('Weighted_Average');
var TF = QueryObj.FieldValue('Total_Forecast');
var AC = QueryObj.FieldValue('Average_Certainty');
var update = "update company set comp_totalforecast = '"+TF+"', comp_weightedaverage = '"+WA+"', comp_averagecertainty = '"+AC+"' from company where comp_companyid ='"+compid+"'";
eWare.ExecSql(update);
}
The code varies the most in the DeleteRecord function because the code runs before the record is deleted so the calculation that is running and updating the fields on the company summary screen are using the same values that I had before. I need the calculations to take place after the record is gone so I can get accurate numbers. Unfortunately, there isn’t a post delete function either so I had to 0 out the forecast and certainty values and then do the calculations based on the total number of opportunities minus 1 accounting for this record I am about to delete. Then I do the update command and the record gets deleted.
In conclusion, the table level script feature is “good” for running JSCRIPT/SQL commands to update data when you are on a different entities but the detached table level script has been proven to be “ugly” and the functionality in the table level script can be “bad”. These are the work arounds I had to use but I am hopeful that Sage will improve the table level script feature in upcoming versions.

