# legacydeveloper Cgcloud Rtr Dev Guide - Full Documentation # Classes Detailed samples of the classes that are used in report configurations. - **[TPM\_RTRFixedFunds\_AMS](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/tpm_rtr_class_tpm_rtrfixedfunds_ams.htm)** Sample of the TPM\_RTRFixedFunds\_AMS class. - **[TPM\_RTRPromoTypeFilter\_AMS](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/tpm_rtr_class_tpm_rtrpromotypefilter_ams.htm)** Sample of the TPM\_RTRPromoTypeFilter\_AMS class. - **[TPM\_RTRReportingParentPromoFilter](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/tpm_rtr_class_tpm_rtrreportingparentpromofilter.htm)** Sample of the TPM\_RTRReportingParentPromoFilter class. # Create a Fund Report with Custom Apex Datasources The data that's required for the reports is available on a Hyperforce server and is interfaced via regular data sources such as AccountMonthlyMeasures or AccountWeeklyMeasures. If you want to integrate KPI values that's obtained from other sources such as a Salesforce attribute, use Real Time Report (RTR) to add a custom datasource. A custom datasource is an Apex endpoint that delivers KPI values to the report. RTR offers you these custom data sources: - RTRSalesforceMonthlyMeasures: Used to integrate monthly values for KPIs. - RTRSalesforceWeeklyMeasures: Used to integrate weekly values for KPIs. Depending on your report, you can use either weekly or monthly custom data sources to fetch KPIs for that time level. Ensure that you also implement an Apex endpoint that corresponds to these data sources that can deliver these KPIs in the corresponding time granularity (week or month). **Snippet to Define Custom Datasources** "datasources": \[ { "name": "AccountMonthlyMeasures" }, { "name": "RTRSalesforceMonthlyMeasures" } \], In addition to defining the datasource in the report configuration, ensure that you also create a base Apex class (based on your use case) and a logic Apex class. # Create Reports with Custom Apex Filter Along with standard filters, you can also define custom Apex filters that reference the attributes of the Promotion object. Core examples are filtering by the promotion type and phase of the promotion. A custom example is linking one promotion to one specific event. With that, the main benefit of a custom Apex filter is to offer the opportunity to filter promotions by promotion attributes that aren’t covered by standard filters. In addition to that, if all the values of the standard filter aren’t visible, you can limit the available filter values of a promotion attribute for a specific real-time report. ![Note](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/images/icon_note.png) #### Note Custom Apex filters work only with single-select picklists, not with multi-select picklists. ## Report Configuration In the report configuration, ensure that you define the custom Apex filters in the same section as standard filters. ![Note](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/images/icon_note.png) #### Note You can't use a standard and custom filter with the same name (for example, promo\_templatesfid) in the same report configuration. ## Example { "type": "singleselect", "label": "Promotion Type", "name": "promo\_templatesfid", "source": { "class": "TPM\_RTRPromoTypeFilter\_AMS", "method": "Event" }, "defaultValue": "<>" Here, method is the name of a parameter. This parameter is the Developer\_Name\_\_c field of a metadata type record. However, you can also write a filter without parameters and discard the method line and modify the Apex code accordingly. ## Apex Class To implement the new filter logic, create an Apex class. You can construct the code for all Apex filters as specified here: 1. Implement System.Callable. 2. Retrieve the user's sales organization for later use. 3. Define the custom logic that returns a map with values. 4. Define a public object call that the report always initially calls. When the public object is called, it calls the method with the custom logic and passes a string parameter (based on the value of method in the report configuration). # Customizing Real Time Reporting (RTR) with APEX You can create a fund report with custom APEX data sources to integrate KPI values that are obtained from other sources such as a Salesforce attribute. You can also create reports with custom APEX filters, along with the standard filters. Although requirements vary across clients and markets, these general requirements apply for different processes: - **Strategic Planning:** Provides flexible views on business metrics (profit, profit margin, ROI, costs, revenue, and so on) on either a regional or national level (across many or all key accounts) to drive future recommendations for target volumes, target revenues, marketing initiatives, brand initiatives, and pricing. - **Fund Management:** Provides you with a hollistic view of the budgeting not only within but also across funds prior to, during, and after a financial year, so that you can control the financials on both an overall and granular level. - **Account Planning:** Provides you with flexible views on a single account to monitor business development within the full P&L (Profit and Loss) for the relevant brands and categories and on a detailed plan product level. - **Promotion Planning:** Provides overviews on key promotion metrics (profit, profit margin, ROI, costs, revenue, etc.) for a single account and time frame on both summary and detailed levels to conduct pre-event and post-event analysis. - **Promotion Execution:** Tracks claims against promotions or tactics in promotion-based reports and fund reporting to be always aware of missing claim information and outstanding deductions. - **Post-Event Analysis:** Evaluates the success of single promotional events, brand and marketing initiatives across accounts, and the overall success rate on account-related contracts and their impact on single accounts. Real Time Reporting supports these requirements by providing a technical foundation to retrieve the important business metrics and allowing flexible configurations to design reports according to client requirements. Example: During an iterative planning processes, John—the Key Account Manager (KAM), identifies gaps in promotion planning and makes the relevant changes. Real Time Reporting (RTR) helps him to view the impact on his account directly after releasing the changes, allowing him to create a more fruitful Account Plan for his retail partner. The immediate effect of changes that reflect in the report helps him to act instantly and adapt promotion planning on the fly. With RTR, John can view the impact on a summary level by either brand or category and on a detailed plan product level. These are the five key functional benefits of RTR: - Supports the full TPM closed loop with real-time insights to drive the decision-making process. - Provides a consistent user experience for CG Cloud TPM users. - Builds the trust of users because the numbers that are shown in RTR and other application areas (for example, P&L sheets) are consistent. - Supports the export of data in MS-Excel format for all reports that allow users to work further on exported data. - Allows you to define user-specific views so that users can switch between reports instantly. # Get Started This document explains the Salesforce configurations required to export CSV files from within Real Time Reporting (RTR) using the Integration API, and provides information on customizing the RTR using APEX. RTR exports support the export of BOM components.
Available in: Lightning Experience in Enterprise and Unlimited Editions that have Consumer Goods Cloud Trade Promotion Management enabled.
To learn more about exporting KPIs, see [Exporting the KPIs from Hyperforce](https://developer.salesforce.com/docs/atlas.en-us.264.0.retail_api.meta/retail_api/kpi_export.htm). # TPM\_RTRFixedFunds\_AMS Sample of the TPM\_RTRFixedFunds\_AMS class. global class TPM\_RTRFixedFunds\_AMS{ global static List getBrand(Date inputDateBegin, Date inputDateEnd, TPM\_RTRReportingWrapper\_AMS.InputPayload payload, List output, Map fixedFunds){ //This code creates a list based on the map that was passed into the method. In this List only the Fund Template //Names are stored so that the list can be used in the AggregateResult query (where clause). The goal is to only load records that //are related to the Fund Templates we're looking at. List FundTemplateTypes = New List(); String SalesOrg; For(String cmID : fixedFunds.keyset()){ FundTemplateTypes.add(fixedFunds.get(cmID).Fund\_Template\_\_c); if(Salesorg == Null){ Salesorg = fixedFunds.get(cmID).Sales\_Org\_\_c; } } //The query returns Fund\_Transaction\_Row records based on the selected timeframe, customer, category and templates //The results are aggregated TPM\_RTR\_Amount values grouped by BRAND, FUND TEMPLATE AggregateResult\[\] groupedResults = \[SELECT SUM(TPM\_RTRAmount\_\_c) amount, cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Fund\_Template\_\_r.Name template, cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Fund\_Template\_\_r.TPM\_RTRRoutingFundType\_\_c RTRRoutingFundType, cgcloud\_\_Fund\_Transaction\_\_r.cgcloud\_\_Fund\_Transaction\_Header\_\_r.TPM\_Product\_\_c brand FROM cgcloud\_\_Fund\_Transaction\_Row\_\_c WHERE cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Fund\_Template\_\_r.TPM\_RTRRoutingFundType\_\_c in :FundTemplateTypes AND cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Sales\_Org\_\_c= :SalesOrg AND cgcloud\_\_Fund\_Transaction\_\_r.cgcloud\_\_Fund\_Transaction\_Header\_\_r.TPM\_Product\_\_r.cgcloud\_\_Criterion\_1\_Product\_\_c IN :payload.productsfids AND cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Anchor\_Account\_\_c IN :payload.accountsfids AND cgcloud\_\_Transaction\_Type\_\_c IN ('Deposit','Withdraw') AND ((cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Valid\_From\_\_c <= :inputDateEnd AND cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Valid\_From\_\_c >= :inputDateBegin) OR (cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Valid\_Thru\_\_c <= :inputDateEnd AND cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Valid\_Thru\_\_c >= :inputDateBegin)) GROUP BY cgcloud\_\_Fund\_Transaction\_\_r.cgcloud\_\_Fund\_Transaction\_Header\_\_r.TPM\_Product\_\_c, cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Fund\_Template\_\_r.Name, cgcloud\_\_Target\_Fund\_\_r.cgcloud\_\_Fund\_Template\_\_r.TPM\_RTRRoutingFundType\_\_c\]; String brand, template, RTRRoutingFundType; Double amount; Map templateMap; Map> cmap = New Map>(); Map RTRRoutingFundTypeMap = New Map(); //This For-Loop works on the aggregateResults and allocates all the records to their respective "place" - //it basically sorts all data into the right maps For(AggregateResult ar : groupedResults){ brand = String.valueOf(ar.get('brand')); //the last part of this line retrieved the brand id of our query (brand is an alias) template = String.valueOf(ar.get('template')); //the last part of this line retrieved the template Name of our query (template is an alias) RTRRoutingFundType = String.valueOf(ar.get('RTRRoutingFundType')); //the last part of this line retrieved the RTR Routing Fund Type of our query (RTRRoutingFundType is an alias)')); amount = Double.valueOf(ar.get('amount')); //the last part of this line retrieved the aggregated amount of our query (amount is an alias) templateMap = New Map(); If(!cmap.containsKey(brand)){ templateMap.put(template, amount); cmap.put(brand, templateMap); } else { templateMap = cmap.get(brand); if(!templateMap.containsKey(template)){ templateMap.put(template, amount); cmap.put(brand, templateMap); } } if(!RTRRoutingFundTypeMap.containskey(RTRRoutingFundType)){ RTRRoutingFundTypeMap.put(RTRRoutingFundType, template); } } //Once all the results of our query are allocated and sorted, the data needs to be written back to the report //For each brand in cmap, For(String brandId : cmap.keyset()){ //and each template for that brand, For(String templateId : cmap.get(brandId).keyset()){ //Now we loop trough the map that was passed to this method For(String ffID : fixedFunds.keyset()){ //to check to which KPI the map we're currently in is corrosponding to If(templateId == RTRRoutingFundTypeMap.get(fixedFunds.get(ffID).Fund\_Template\_\_c)){ //If the map we're working on right now has the RDF Fund Template, and one of the records in our map has //the RDF Fund Template assigned, we write back this KPI to the corrosponding KPI Definition of the Custom Meta Data output.add(new TPM\_RTRReportingWrapper\_AMS.OutputRecord(String.valueOf(brandId), String.valueOf(fixedFunds.get(ffID).KPI\_Definition\_\_c), 'Total', Double.valueOf(cmap.get(brandId).get(templateId)))); } } } } return output; } global static List getCategory(Date inputDateBegin, Date inputDateEnd, TPM\_RTRReportingWrapper\_AMS.InputPayload payload, List output, Map fixedFunds){ //This code creates a list based on the map that was passed into the method. In this List only the Fund Template //Names are stored so that the list can be used in the AggregateResult query. The goal is to only load records that //are related to the Fund Templates we're looking at. List FundTemplateTypes = New List(); String SalesOrg; For(String cmID : fixedFunds.keyset()){ FundTemplateTypes.add(fixedFunds.get(cmID).Fund\_Template\_\_c); if(SalesOrg == Null){ SalesOrg = fixedFunds.get(cmID).Sales\_Org\_\_c; } } //Calculation of the current week number Date todaysDate = date.today(); Date todaysDateInstance = date.newInstance(todaysdate.year(), todaysdate.month(), todaysdate.day()); todaysdate.day(); Integer currentyear = todaysdate.year(); Date startDate = date.newInstance(currentyear, 1,1); Integer numberDaysDue = startDate.daysBetween(todaysDateInstance); Integer numberOfWeek = math.mod(Integer.valueOf(math.floor((numberDaysDue))/7),52)+1; //Support Map for NA\_REP\_FixedFund\_A Calculation Map mPrdAmounts = new Map(); //Query AggregateResult\[\] groupedResults = \[SELECT SUM(cgcloud\_\_Deposits\_Approved\_\_c) sumValue, cgcloud\_\_Anchor\_Product\_\_c, cgcloud\_\_Fund\_Template\_\_c, cgcloud\_\_Fund\_Template\_\_r.Name tmplName, cgcloud\_\_Fund\_Template\_\_r.TPM\_RTRRoutingFundType\_\_c RTRRoutingFundType FROM cgcloud\_\_Fund\_\_c WHERE cgcloud\_\_Fund\_Template\_\_r.TPM\_RTRRoutingFundType\_\_c IN :FundTemplateTypes AND cgcloud\_\_Sales\_Org\_\_c= :SalesOrg AND cgcloud\_\_Anchor\_Product\_\_c IN :payload.productsfids AND cgcloud\_\_Anchor\_Account\_\_c IN :payload.accountsfids AND ((cgcloud\_\_Valid\_From\_\_c <= :inputDateEnd AND cgcloud\_\_Valid\_From\_\_c >= :inputDateBegin) OR (cgcloud\_\_Valid\_Thru\_\_c <= :inputDateEnd AND cgcloud\_\_Valid\_Thru\_\_c >= :inputDateBegin)) GROUP BY cgcloud\_\_Anchor\_Product\_\_c, cgcloud\_\_Fund\_Template\_\_c, cgcloud\_\_Fund\_Template\_\_r.Name, cgcloud\_\_Fund\_Template\_\_r.TPM\_RTRRoutingFundType\_\_c\]; For (AggregateResult ar : groupedResults){ //Check if current product is not already in the map, if so, add to the map with value 0 If(!mPrdAmounts.containsKey(String.valueOf(ar.get('cgcloud\_\_Anchor\_Product\_\_c')))){ mPrdAmounts.put(String.valueOf(ar.get('cgcloud\_\_Anchor\_Product\_\_c')), 0); } //Add the sum of the aggregateResult to the existing value in map for current product mPrdAmounts.put(String.valueOf(ar.get('cgcloud\_\_Anchor\_Product\_\_c')), mPrdAmounts.get(String.valueOf(ar.get('cgcloud\_\_Anchor\_Product\_\_c'))) + Double.valueOf(ar.get('sumValue'))); //Check Fund Template and add KPI For(String qKPI : fixedFunds.keyset()){ If(String.valueOf(ar.get('RTRRoutingFundType')) == fixedFunds.get(qKPI).Fund\_Template\_\_c){ output.add(new TPM\_RTRReportingWrapper\_AMS.OutputRecord(String.valueOf(ar.get('cgcloud\_\_Anchor\_Product\_\_c')), String.valueOf(fixedFunds.get(qKPI).KPI\_Definition\_\_c), 'Total', Double.valueOf(ar.get('sumValue')))); } } } //Add NA\_REP\_FixedFund\_A if required If(fixedFunds.containsKey('NA\_REP\_FixedFund\_A')){ For(String cdr : mPrdAmounts.keyset()){ output.add(new TPM\_RTRReportingWrapper\_AMS.OutputRecord(String.valueOf(cdr), 'NA\_REP\_FixedFund\_A', 'Total', Double.valueOf((mPrdAmounts.get(cdr)\*numberOfWeek/52)))); } } return output; } } # TPM\_RTRFixedFunds\_AMS (The Logic Class) The actual logic is defined in this class. The benefit of routing to a separate third class is that the code runs only when needed. For example, the Apex classes run only when the report contains the custom datasource and any of the KPIs defined in the custom metadata. If the report contains fixed funds KPIs only on the brand level, then only that particular method is run while the method that's related to categories is skipped. Also if the report contains fixed funds on brand level for only one particular template, then only this particular KPI is aggregated, calculated, and returned. It’s important to see how to write back values to the report. For the fixed funds logic, grouping, aggregations, and calculations are performed. And when that logic is performed, the values are written back to the report. This is done by adding the values to the output list and then returning it. The OutputRecord method in the wrapper class creates the output list and contains product, KPI, time, and value. # TPM\_RTRFunds\_AMS (The Logic Class) Use RTRSalesforceMonthlyMeasures as the datasource to execute the base classes by every report. This function performs these steps: 1. Defines a list of output record items. 2. Retrieves the fund amounts from Salesforce data by using SOQL, depending on the use case. In this example, only funds at the category level are shown. You can use the category filter as the selection criterion. The funds are also limited to the account and time frame that's selected in the filter. 3. For every result record, the function creates an output record and adds it to the list of results. **Sample Code** global with sharing class TPM\_RTRFunds\_AMS{ global static List doPost(Date inputDateBegin , Date inputDateEnd, TPM\_RTRReportingWrapper\_AMS.InputPayload payload) { List output = new List (); String pdim ; String kdim ; String tdim ; Double v ; AggregateResult\[\] groupedResults = \[SELECT cgcloud\_\_Anchor\_Product\_\_c, sum (cgcloud\_\_Deposits\_Approved\_\_c ) sumValue from cgcloud\_\_Fund\_\_c WHERE cgcloud\_\_Anchor\_Product\_\_c IN :payload.productsfids AND cgcloud\_\_Anchor\_Account\_\_c IN :payload.accountsfids AND ((cgcloud\_\_Valid\_From\_\_c <= :inputDateEnd AND cgcloud\_\_Valid\_From\_\_c >= :inputDateBegin) OR (cgcloud\_\_Valid\_Thru\_\_c <= :inputDateEnd AND cgcloud\_\_Valid\_Thru\_\_c >= :inputDateBegin)) group by cgcloud\_\_Anchor\_Product\_\_c \]; for(Integer i=0; i < groupedResults.size(); i++) { v =(Double) groupedResults\[i\].get('sumValue'); pdim =(String) groupedResults\[i\].get('cgcloud\_\_Anchor\_Product\_\_c'); kdim = 'myFndAmount'; tdim = 'Total'; output.add(new TPM\_RTRReportingWrapper\_AMS.OutputRecord(pdim ,kdim, tdim, v)); } return output; } } # TPM\_RTRPromoTypeFilter\_AMS Sample of the TPM\_RTRPromoTypeFilter\_AMS class. public with sharing class TPM\_RTRPromoTypeFilter\_AMS implements System.Callable { public class TPM\_RTRPromotionsCallableException extends Exception {} private static String userSalesOrgName = null; //GET SF ID static { List users = \[SELECT cgcloud\_\_Sales\_Org\_\_c FROM User WHERE Id = :String.escapeSingleQuotes(UserInfo.getUserId()) LIMIT 1\]; userSalesOrgName = users\[0\].cgcloud\_\_Sales\_Org\_\_c; } @testVisible private static List> getPromotionTypes(String devname) { String myCM; 1. Query the custom metadata record based on the parameter, and then perform workarounds for the test classes. //GET CUSTOM METADATA TYPE PROMO TYPE GROUPINGS (BASED ON ARGUMENT OF REPORT) if(!Test.isRunningTest()){ TPM\_RTRPromoTypes\_\_mdt customMetaData = \[SELECT Promo\_Types\_\_c FROM TPM\_RTRPromoTypes\_\_mdt WHERE DeveloperName = :devname LIMIT 1\]; myCM = customMetaData.Promo\_Types\_\_c; } //WORKAROUND FOR TESTCLASS (CUSTOM METADATA TYPE) else { myCM = devname; } 2. From the custom metadata record, split the value of the Promo\_Types\_\_c field into individual values and then parse them into a new list type called groups. List splitGrouping = myCM.split(';'); List typeGroups = New List(); For(String item : splitGrouping){ typeGroups.add(item.trim()); } 3. Create a new list that you can later use to store the value of results = List>. You can also query promotion templates into another active list that has the same sales org as the user and whose name matches the values in the Groups list type. List> result = new List>(); List prmTmpl = New List(); prmTmpl = \[SELECT Id, Name FROM cgcloud\_\_Promotion\_Template\_\_c WHERE cgcloud\_\_Active\_\_c = true AND cgcloud\_\_Sales\_Org\_\_c = :userSalesOrgName AND Name IN :typeGroups\]; Map outP = New Map(); 4. Return the result. for(cgcloud\_\_Promotion\_Template\_\_c tmpl : prmTmpl){ If(!outP.containsKey(tmpl.Name)){ outP.put(String.valueOf(tmpl.Id), String.valueOf(tmpl.Name)); } } for(String typeId : outP.keyset()){ result.add(new Map { 'label' => outP.get(typeId), 'value' => typeId }); } return result; } public Object call(String method, Map args) { return getPromotionTypes(method); } } # TPM\_RTRReportingParentPromoFilter Sample of the TPM\_RTRReportingParentPromoFilter class. public with sharing class TPM\_RTRReportingParentPromoFilter implements System.Callable { public class TPM\_RTRPromotionsCallableException extends Exception {} private static List users = \[SELECT cgcloud\_\_Sales\_Org\_\_c FROM User WHERE Id = :String.escapeSingleQuotes(UserInfo.getUserId()) LIMIT 1\]; private static String userSalesOrgName = users\[0\].cgcloud\_\_Sales\_Org\_\_c; 1. Create a new list that you can later use to store the value of results = List>. @TestVisible private static List> getParentPromotion() { List> result = new List>(); 2. Query the Promotion object on all records templates that have the CustomerEvents picklist value selected on the TPM\_Promo\_Type\_ControlView\_\_c field for the appropriate sales org ad user. List prmTmpl = \[SELECT ID, Name, cgcloud\_\_Slogan\_\_c , cgcloud\_\_Anchor\_Account\_\_c,cgcloud\_\_Anchor\_Account\_\_r.Name,cgcloud\_\_Promotion\_Template\_\_r.cgcloud\_\_Description\_\_c,cgcloud\_\_Promotion\_Template\_\_r.cgcloud\_\_Sales\_Org\_\_c,cgcloud\_\_Promotion\_Template\_\_r.TPM\_Promo\_Type\_ControlView\_\_c FROM cgcloud\_\_Promotion\_\_c WHERE cgcloud\_\_Promotion\_Template\_\_r.TPM\_Promo\_Type\_ControlView\_\_c ='CustomerEvent' and cgcloud\_\_Promotion\_Template\_\_r.cgcloud\_\_Sales\_Org\_\_c =: userSalesOrgName \]; Map outP = New Map(); for(cgcloud\_\_Promotion\_\_c templ : prmTmpl){ If(!outP.containsKey(templ.ID)){ outP.put(String.valueOf(templ.Id), String.valueOf(templ. cgcloud\_\_Slogan\_\_c)); } } 3. Return the list. for(String statusTo : outP.keyset()){ result.add(new Map { 'label' => outP.get(statusTo), 'value' => statusTo }); } system.debug(json.serialize(result)); return result; } public Object call(String method, Map args) { return getParentPromotion(); } } # TPM\_RTRReportingWrapper\_AMS (Base Class) TPM\_RTRReportingWrapper\_AMS is a base Apex class that receives the payload that's constructed by filter selection in the report. The payload contains the KPIs that are defined in the report, the customer, dates, and other important attributes. It then deserializes the payload into an object that can be used in all other related classes. ![Note](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/images/icon_note.png) #### Note Ensure that this wrapper class name is the same as the one in the Fund Amount use case. However, to incorporate this class with the fund amount scenario, modify the class. **Sample Code** global with sharing class TPM\_RTRReportingWrapper\_AMS { global class Periodmonth { global Integer year; global Integer start; global Integer total; } global class InputPayload { global List accountsfids; global List productsfids; global Periodmonth periodmonth; global String timelevel; global List readproductsfids; global List kpis; global String responsetype; global Boolean error; } global class OutputRecord { global String pdim; global String kdim; global String tdim; global Double v; global OutputRecord(String pdim, String kdim, String tdim, Double v) { this.pdim = pdim; this.kdim = kdim; this.tdim = tdim; this.v = v; } } } # TPM\_RTRReportingWrapper\_AMS (Base Class) TPM\_RTRReportingWrapper\_AMS is a base class that contains the structures to load the input. This class contains the list of KPIs and filter criteria that you chose in the report—selected time frame or the list of customers and products. Depending on various use cases, the payload method can contain different attributes. The OutputRecord method returns product (pdim), KPI (kdim), time (tdim), and values (v). The values are defined in the actual logic class, which is TPM\_RTRFunds\_AMS in this use case. For example, you can use tdim to display values on a more granular view—weeks, months, or quarters. If a report meta has monthly values, the external datasource can also provide monthly values. ![Note](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/images/icon_note.png) #### Note Ensure that kdim contains the name of the fund KPI. **Sample Code** global with sharing class TPM\_RTRReportingWrapper\_AMS { global class PeriodMonth { global Integer year; global Integer start; global Integer total; } global class InputPayload{ global List accountsfids; global List productsfids; global PeriodMonth periodmonth; global List kpis; global String responsetype; global String callingsfuser; } global class OutputRecord{ global String pdim; global String kdim; global String tdim; global Double v; global OutputRecord( String pdim, String kdim , String tdim , Double v){ this.pdim = pdim; this.kdim = kdim; this.tdim = tdim; this.v = v; } } } # TPM\_RTRSalesforceMonthlyMeasures\_AMS (Base Class) TPM\_RTRSalesforceMonthlyMeasures\_AMS is one of the base classes that's used for code-routing and is required to retrieve the information by a Salesforce REST endpoint (/services/apexrest/RTRSalesforceMonthlyMeasures). The system sends the selected filter criteria and the list of KPIs as input payload to the REST endpoint. The APEX REST method then returns the fund amounts as output records to show in the report. The fund values are returned by a function that's executed when a POST request is sent. These steps are run within the Apex function: 1. The payload with filter values is converted into a format that can be used in the Apex code. 2. The time range (start and end date) is built by the selected month information. 3. The function that contains the logic for this use case is invoked. **Sample Code** @RestResource(urlMapping='/RTRSalesforceMonthlyMeasures/\*') global with sharing class TPM\_RTRSalesforceMonthlyMeasures { @httpPost global static List doPost() { String requestBody = RestContext.request.requestbody.tostring(); TPM\_RTRReportingWrapper\_AMS.InputPayload payload = (TPM\_RTRReportingWrapper\_AMS.InputPayload) JSON.deserialize (requestBody , TPM\_RTRReportingWrapper\_AMS.InputPayload.class); Date inputDateBegin = Date.newInstance(payload.periodmonth.year,payload.periodmonth.start,1); Date inputDateEnd = inputDateBegin.addMonths( payload.periodmonth.total ); return TPM\_RTRFunds\_AMS.doPost( inputDateBegin , inputDateEnd, payload); } } # TPM\_RTRSalesforceMonthlyMeasures\_AMS (Base Class) TPM\_RTRSalesforceMonthlyMeasures\_AMS is the second base class that's used for code-routing and is required to retrieve the information by a Salesforce REST endpoint (/services/apexrest/RTRSalesforceMonthlyMeasures). @RestResource(urlMapping='/RTRSalesforceMonthlyMeasures') global with sharing class TPM\_RTRSalesforceMonthlyMeasures\_AMS { @HttpGet global static String doGet(){ return 'Hello world!'; } @HttpPost global static List doPost(){ RestContext.response.statuscode = 200; These are the code details: 1. The payload is received, and is then turned into an object of our wrapper class. The payload contains the KPIs that are defined in the report configuration. String requestBody = RestContext.request.requestBody.toString(); TPM\_RTRReportingWrapper\_AMS.InputPayload payload = (TPM\_RTRReportingWrapper\_AMS.InputPayload) JSON.deserialize(requestBody, TPM\_RTRReportingWrapper\_AMS.InputPayload.class); System.debug(payload); if(payload.error == true) { RestContext.response.statuscode = 500; return null; } 2. The integration user runs the base classes, not the user who opens the report. As a result, ensure that you query an account on the payload that the user selected to retrieve the actual user's sales org. //Get Sales Org of the user who initiated the report Account acc = \[SELECT Id, cgcloud\_\_Sales\_Org\_\_c FROM Account WHERE Id IN :payload.accountsfids LIMIT 1\]; String userSFOrg = acc.cgcloud\_\_Sales\_Org\_\_c; 3. Helper maps and lists are created. //Helper Maps/List List output = new List(); Map categoryKPIs = New Map(); Map brandKPIs = New Map(); 4. The dates from the payload are converted to a workable format. //Loading and transforming the dates selected in the filter Date inputDateBegin = Date.newInstance(payload.periodmonth.year, payload.periodmonth.start+1, 1); Date inputDateEnd = inputDateBegin.addMonths(payload.periodmonth.total).addDays(-1); 5. The Apex class contains use case-specific code. Here, you can query all custom metadata records that were defined earlier and that belong to the sales org of the user. Then you can check whether the payload contains any KPIs in the custom metadata records. If it does, verify the level on which those KPIs are available and then add them to a dedicated map (category KPIs or brand KPIs map). //Check against custom metadata if payload contains relevant KPIs and sort them For(TPM\_RTRRouting\_\_mdt kpi : \[SELECT Id, KPI\_Definition\_\_c, KPI\_Level\_\_c, Fund\_Template\_\_c, Sales\_Org\_\_c FROM TPM\_RTRRouting\_\_mdt WHERE Sales\_Org\_\_c = :userSFOrg\]){ If(payload.kpis.contains(kpi.KPI\_Definition\_\_c)){ If(kpi.KPI\_Level\_\_c == 'Category'){ categoryKPIs.put(kpi.KPI\_Definition\_\_c, kpi); } Else If(kpi.KPI\_Level\_\_c == 'Brand'){ brandKPIs.put(kpi.KPI\_Definition\_\_c, kpi); } } } 6. You can check whether the two maps (brandKPIs and categoryKPIs) contain any values — if not, nothing happens. If they do, the actual class where all the logic happens is called and parameters such as the payload are passed. //Run logic try{ If(!categoryKPIs.isEmpty()){ TPM\_RTRFixedFunds\_AMS.getCategory(inputDateBegin, inputDateEnd, payload, output, categoryKPIs); } If(!brandKPIs.isEmpty()){ TPM\_RTRFixedFunds\_AMS.getBrand(inputDateBegin, inputDateEnd, payload, output, brandKPIs); } } catch(Exception ex){ RestContext.response.statuscode = 400; System.debug(ex); } return output; } } # Use Case: Fixed Funds In this use case, the custom datasource is used to include a fixed fund KPI in Real Time Report (RTR). As a result, the fixed funds are aggregated on both category and brand levels. One visualization option is this report, in which a brand value is only available for one brand. However, this value is not summed up to the category level. For the calculation of category levels, the data that's stored on Salesforce and Processing Services is used. ## Prerequisites 1. Create a KPI definition for the fund amount. For this KPI definition, ensure that the object scope is Account and it has the writeback feature enabled. ![Note](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/images/icon_note.png) #### Note This KPI definition only provides a name for the external KPI. The Apex class defines how to enter values in this KPI. 2. Enter the name of the datasource table as Account Measure. 3. Add the KPI to the Reporting KPI set that's used in the report. ## Report Configuration 1. Create new KPI definitions for each use case, such as Fixed RDF Brand. Ensure that the object scope of these KPI definitions is Account. 2. Enter the name of the datasource table as Account Measure. 3. Define these KPI definitions in the report configuration 4. Create a custom metadata type, TPM\_RTRRouting\_\_mdt, which is linked to the KPI definition, sales organization, fund template, and KPI levels. | Field API Name | Type | Description | | --- | --- | --- | | Fund\_Template\_\_c | Text | Determines the KPI to which the fixed funds are to be attributed. For example, All Transaction Row Records, in which the target fund has the RDF Brand Fund that belongs to the Fixed RDF KPI. | | KPI\_Definition\_\_c | Text | Name of the Fixed Funds KPI (such as FXDR and FTDR) that determines the data which should be returned from the logic in the Apex class. | | KPI\_Level\_\_c | Text | Fixed funds can be stored on different levels (currently, category and brand). This is needed to determine the method that should be run. (category and brand use different logic.) | | Sales\_Org\_\_c | Text | Sales organization to which the record belongs. | 5. On the cgcloud\_\_Fund\_Transaction\_Row\_\_c object, add a new field.
Field API Name Type Description Formula
TPM_RTRAmount__c Formula (Double) Fixed Funds for Brands are calculated based on the value of the Amount field on the transaction record. Depending on the transaction type (withdraw or deposit), the sum of the aggregated amounts either increases (deposit) or decreases (withdraw).

The formula field turns the amounts of records with transaction type = deposit to negative values. Instead of aggregating the amount field, you can aggregate the TPM_RTR_Amount__c field.

IF(ISPICKVAL( cgcloud__Transaction_Type__c , 'Withdraw'), cgcloud__Amount__c * (-1), cgcloud__Amount__c )
6. On the cgcloud\_\_Fund\_Transaction\_Header\_\_c object, add a new field. | Field API Name | Type | Description | | --- | --- | --- | | TPM\_Product\_\_c | Lookup(Product) | Used to link products to the Multi-Fund Transaction object. | 7. On the cgcloud\_\_Fund\_Template\_\_c object, add a new field. | Field API Name | Type | Description | | --- | --- | --- | | TPM\_RTRRoutingFundType\_\_c | Picklist | Used to filter fund template records. The picklist values of this field are fund template record names. | ![Note](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/images/icon_note.png) #### Note Ensure that you create new fund templates for each case, such as RDF Brand Fund. Maintain the newly created TPM\_RTRRoutingFundType\_\_c field on the fund template. ## Apex Classes Custom data sources are related to two base Apex classes. Because all reports share these classes, you can use both classes only for code-routing, not to perform any actual logic, queries, or calculations. ![Note](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/images/icon_note.png) #### Note In RTR, the integration user runs both Apex classes, not the user who opens the report. As a result, the integration user must have access permissions for the used Apex classes. - **[TPM\_RTRReportingWrapper\_AMS (Base Class)](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/tpm_rtr_class_tpm_rtrreportingwrapper2_ams.htm)** TPM\_RTRReportingWrapper\_AMS is a base class that contains the structures to load the input. This class contains the list of KPIs and filter criteria that you chose in the report—selected time frame or the list of customers and products. Depending on various use cases, the payload method can contain different attributes. - **[TPM\_RTRSalesforceMonthlyMeasures\_AMS (Base Class)](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/tpm_rtr_class_rtrsalesforcemonthlymeasures2_ams.htm)** TPM\_RTRSalesforceMonthlyMeasures\_AMS is the second base class that's used for code-routing and is required to retrieve the information by a Salesforce REST endpoint (/services/apexrest/RTRSalesforceMonthlyMeasures). - **[TPM\_RTRFixedFunds\_AMS (The Logic Class)](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/tpm_rtr_class_tpm_rtrfixedfunds2_ams.htm)** The actual logic is defined in this class. The benefit of routing to a separate third class is that the code runs only when needed. # Use Case: Fund Amount The Fund object stores the fund amount in Salesforce. To show a fund amount for a particular category or brand, fetch it from the Salesforce database by using custom data sources. ## Prerequisites 1. Create a KPI definition for the fund amount. For this KPI definition, ensure that the object scope is Account and it has the writeback feature enabled. ![Note](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/images/icon_note.png) #### Note This KPI definition only provides a name for the external KPI. The Apex class defines how to input values in this KPI. 2. Enter the name of the datasource table as Account Measure. 3. Add the KPI to the Reporting KPI set that's used in the report. ## Report Configuration Apart from defining RTRSalesforceMonthlyMeasures as an additional datasource in the report configuration, ensure that you add the newly created KPI to the list of measures. { "label": "Fund Amount", "name": "myFndAmount" }, ## Apex Classes The custom data sources are related to two base Apex classes. You can use both classes only for code-routing and not to perform any actual logic, queries, or calculations; this is because all reports share these classes. ![Note](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/images/icon_note.png) #### Note In RTR, integration users run both the Apex classes, not the user who opens the report. As a result, integration users must have access permissions to the used Apex classes. - **[TPM\_RTRReportingWrapper\_AMS (Base Class)](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/tpm_rtr_class_tpm_rtrreportingwrapper_ams.htm)** TPM\_RTRReportingWrapper\_AMS is a base Apex class that receives the payload that's constructed by filter selection in the report. The payload contains the KPIs that are defined in the report, the customer, dates, and other important attributes. It then deserializes the payload into an object that can be used in all other related classes. - **[TPM\_RTRSalesforceMonthlyMeasures\_AMS (Base Class)](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/tpm_rtr_class_rtrsalesforcemonthlymeasures_ams.htm)** TPM\_RTRSalesforceMonthlyMeasures\_AMS is one of the base classes that's used for code-routing and is required to retrieve the information by a Salesforce REST endpoint (/services/apexrest/RTRSalesforceMonthlyMeasures). - **[TPM\_RTRFunds\_AMS (The Logic Class)](https://developer.salesforce.com/docs/atlas.en-us.cgcloud_rtr_dev_guide.meta/cgcloud_rtr_dev_guide/tpm_rtr_class_rtrfunds_ams.htm)** Use RTRSalesforceMonthlyMeasures as the datasource to execute the base classes by every report.