Thursday, 6 September 2018

Dell Boomi: How to query data from MYSQL using stored Procedure

 How to query data from MYSQL using stored Procedure

When I normally integrate database with Salesforce using Dell Boomi i use views to query data from database, but due to some complex query structure not supported by MYSQL we ended by changing our approach and start querying data from a stored procedure.

Dell Boomi support querying data via a stored procedure, but there is a well known error you may face when try this feature.Before going towards error i first let you know how to get data using a stored procedure.

We have to create a "Get" database operation then create a database profile.In the "Type" fields select "Stored Procedure" rather than query.

Then import profile using stored procedure name from your database.When import profiles completed you wont be able to see any child elements in profile. You have to manually create all elements in profiles and then map.

This is it....

Now come to error part when you will try to run this process you might face an error saying "stored procedure not found". This is Dell Boomi known error. What happen is that when you import stored procedure from your database boomi automaically append database name with stored procedure name and when try to run it gives error.

Please remove the schema-Name from the Stored Procedure name in the Database profile after Importing. For example, after you import the Stored Procedure, please change the name from 'OLTP_Symitar.IRA_ScoreCard' to 'IRA_ScoreCard' and Save the profile.


Error Solved :)

Friday, 6 April 2018

Dell Boomi: Integrate Salesforce with Jira using Dell Boomi

Dell Boomi: Integrate Salesforce with Jira using Dell Boomi

Requirement:


Cases created in Salesforce should be integrate with Issues in Jira using Dell Boomi.
  • Only specific issues needs to be integrated.
  • Issues which needs to be integrated will have Jira issue Id to make a connection.
  • If comments are added on cases which have issue id, then comments will be added in jira against same issue.
  • Comments update should work in same manner.

Solution:

Boomi Connector:

Use Jira Rest connector of Boomi. Give Url , user name and password to built connection.

Jira Rest Operation:

Before configuring operation you have to select your DML. If you want to create,delete, query or update.

For Create Comments:

Click Import.
Select cloud Atom and connection we just created.
Click Next button.
If your connection is valid you will get list of object from Jira.
Select Issue comments from drop down and this will create a profile for you.

Map:

Craete a new map select salesforce as source profile and Jira profile as destination.
Link issue id and comments.

Process will run now.

Note: For comments update operation you have to provide comment id too.
For Description Update operation Issue id and description is needed.



Thursday, 15 March 2018

Dell Boomi: How to connect CloudSQL from Dell Boomi

How to connect CloudSQL from Dell Boomi

Recently I got a requirements from client that we have to connect CloudSQL via Dell Boomi, so the exact requirement was to query data from Salesforce and push it to CloudSQL's specific project which is using MYSQL as a database.

Dell Boomi do not have any dedicated connector for cloudSql connection. As you all know that cloudSql is on cloud so first thought we have to connect such system is to use HTTP connector of dell Boomi. Just to give you a little overview of Dell Boomi HTTP connector, It is used to conect any system via API.Get more information from here.

Now issue is CloudSql even being on cloud does not support connecting it via API and doing DML operations. Surely you can connect it via API but this API will only allow you to do some Admin work. Get from Information here.

So the option we are left with is use Database connector of Dell Boomi and use cloud atom to connect CloudSQL database.

Process

Connection

Note:
Use cloud atom to run this integration.
In creating database profile if you select dynamic insert this will allow you to import database structure from SQL using your connection, creating profile manually in dell boomi can cause diffrent error like 404 and other.


Tuesday, 30 January 2018

Advanced Apex Specialist Superbadge

Advanced Apex Specialist Superbadge


Step 1:

Constants Class:


public class Constants{
    public static final Integer DEFAULT_ROWS = 5;
    public static final String SELECT_ONE = Label.Select_One;
    public static final String INVENTORY_LEVEL_LOW = Label.Inventory_Level_Low;
    public static final List<Schema.PicklistEntry> PRODUCT_FAMILY = Product2.Family.getDescribe().getPicklistValues();
    public static final String DRAFT_ORDER_STATUS = 'Draft';
    public static final String ACTIVATED_ORDER_STATUS = 'Activated';
    public static final String INVENTORY_ANNOUNCEMENTS = 'Inventory Announcements';
    public static final String ERROR_MESSAGE = 'An error has occurred, please take a screenshot with the URL and send it to IT.';
    public static final Id STANDARD_PRICEBOOK_ID = '01s1N000007brPH';
}




Create Custom Metadata type


Step 2:

Order Trigger:

trigger orderTrigger on Order (after update)
    
 {
     OrderHelper.AfterUpdate(Trigger.New, Trigger.Old);

}

Order Helper

public without sharing class OrderHelper { public static void AfterUpdate(List<Order> newList, List<Order> oldList){ Set<Id> orderIds = new Set<Id>(); for ( Integer i=0; i< newList.size(); i++ ){ if ( newList[i].Status == Constants.ACTIVATED_ORDER_STATUS && oldList[i].Status == Constants.DRAFT_ORDER_STATUS ){ orderIds.add(newList[i].Id); } } RollUpOrderItems(orderIds); } public static void RollUpOrderItems(Set<Id> activatedOrderIds){ Map<Id, Product2> productMap = new Map<Id, Product2>(); for(OrderItem orderLine : [SELECT Id, Product2Id, Product2.Quantity_Ordered__c, Quantity, Order.ActivatedDate FROM OrderItem WHERE OrderId IN : activatedOrderIds]){ if(!productMap.containsKey(orderLine.Product2Id)) productMap.put(orderLine.Product2Id, new Product2(Id =orderLine.Product2Id, Quantity_Ordered__c=0)); } for(AggregateResult ag : [SELECT Sum(Quantity), Product2Id FROM OrderItem WHERE Product2Id IN : productMap.keySet() Group By Product2Id]){ Id product2Id = (Id)ag.get('Product2Id'); Product2 prod = productMap.get(product2Id); prod.Quantity_Ordered__c = (Decimal)ag.get('expr0'); productMap.put(product2Id , prod); } try { if(productMap.values() != null && productMap.values().size() > 0){ update productMap.values(); } }catch ( Exception e ){ System.debug('#### Exception Executed : '+e.getStackTraceString()); } } }

Step 3:

Product2New Page

<apex:page standardController="Product2" extensions="Product2Extension"> <apex:sectionHeader title="New Product" subtitle="Add Inventory" /> <apex:pageMessages id="pageMessages" /> <apex:form id="form" > <apex:actionRegion > <apex:pageBlock title="Existing Inventory" > <apex:chart height="250" width="350" data="{!Inventory}"> <apex:axis type="Numeric" position="left" fields="data" title="MT WON" /> <apex:axis type="Category" position="bottom" fields="name" title="Month"/> <apex:barSeries orientation="horizontal" axis="left" xField="data" yField="name" /> </apex:chart> </apex:pageBlock> <apex:pageBlock title="New Products" > <apex:pageBlockButtons location="top"> <apex:commandButton action="{!save}" value="Save" /> </apex:pageBlockButtons> <apex:pageBlockButtons location="bottom"> <apex:commandButton action="{!addRows}" value="Add" reRender="orderItemTable,pageMessages"/> </apex:pageBlockButtons> <apex:pageBlockTable value="{!productsToInsert}" var="p" id="orderItemTable" > <apex:column headerValue="{!$ObjectType.Product2.fields.Name.label}" > <apex:inputText value="{!p.productRecord.Name}" /> </apex:column> <apex:column headerValue="{!$ObjectType.Product2.fields.Family.label}" > <apex:selectList value="{!p.productRecord.Family}" size="1" multiselect="false"> <apex:selectOptions value="{!FamilyOptions}"/> </apex:selectList> </apex:column> <apex:column headerValue="{!$ObjectType.Product2.fields.IsActive.label}" > <apex:inputField value="{!p.productRecord.isActive}" /> </apex:column> <apex:column headerValue="{!$ObjectType.pricebookEntry.fields.UnitPrice.label}" > <apex:inputField value="{!p.pricebookEntryRecord.UnitPrice}" /> </apex:column> <apex:column headerValue="{!$ObjectType.Product2.fields.Initial_Inventory__c.label}" > <apex:inputField value="{!p.productRecord.Initial_Inventory__c}" /> </apex:column> </apex:pageBlockTable> </apex:pageBlock> </apex:actionRegion> </apex:form> </apex:page>

Product2Extension


public class Product2Extension {

    //public List<Product2> productsToInsert {get;set;}
    public List<ProductWrapper> productsToInsert {get;set;}
    
    public Product2Extension(ApexPages.StandardController controller){
        productsToInsert = new List<ProductWrapper>();
        addRows();
    }

    public void AddRows(){
        for ( Integer i=0; i<Constants.DEFAULT_ROWS; i++ ){
            productsToInsert.add( new productWrapper() );
        }
    }
    //Get family options
    public List<SelectOption> GetFamilyOptions() {
        List<SelectOption> options = new List<SelectOption>{
            new SelectOption(Constants.SELECT_ONE, Constants.SELECT_ONE)
        };
        for (Schema.PicklistEntry ple : Constants.PRODUCT_FAMILY) {
            options.add(new SelectOption(ple.getValue(), ple.getLabel()));
        }
        return options;
    }
    
    public List<ChartHelper.ChartData> GetInventory(){
        return ChartHelper.GetInventory();
    }

    public PageReference Save(){
        Savepoint sp = Database.setSavepoint();
        try {
            List<Product2> products = new List<Product2>();
            List<PricebookEntry> entries = new List<PricebookEntry>();
            for (ProductWrapper wrp : productsToInsert){
                if(wrp.productRecord  != null && wrp.pricebookEntryRecord != null){
                    if(wrp.productRecord.Name != null && wrp.productRecord.Family != null && 
                        wrp.productRecord.Family != Constants.SELECT_ONE && wrp.productRecord.Initial_Inventory__c != null &&
                        wrp.pricebookEntryRecord.UnitPrice != null){
                        
                        products.add(wrp.productRecord);
                        PricebookEntry entry=wrp.pricebookEntryRecord;
                        entry.IsActive=true;
                        entry.Pricebook2Id=constants.STANDARD_PRICEBOOK_ID;
                        entries.add(entry);  
                    }
                }
            }
             insert products; 
             for (integer itr=0; itr<entries.size();itr++){
                entries[itr].Product2Id=products[itr].id;
            }
            insert entries;
            
            //If successful clear the list and display an informational message
            apexPages.addMessage(new ApexPages.message(ApexPages.Severity.INFO,productsToInsert.size()+' Inserted'));
            productsToInsert.clear();   //Do not remove
            addRows();  //Do not remove
        } catch (Exception e){
            Database.rollback(sp);
            apexPages.addMessage(new ApexPages.message(ApexPages.Severity.ERROR,constants.ERROR_MESSAGE));
        }
        return null;
    }
    //Product Wrapper Class
    public class ProductWrapper{
        public product2 productRecord {get;set;}
        public pricebookEntry pricebookEntryRecord{get;set;}
        public productWrapper(){
            productRecord = new product2(Initial_Inventory__c =0);
            pricebookEntryRecord = new pricebookEntry(Unitprice=0.0);
        }
    }

}

ChartHelper class

public without sharing class ChartHelper { @AuraEnabled public static List<chartData> GetInventory(){ List<chartData> cht = new List<chartData>(); for (AggregateResult ar : [SELECT Family, SUM(Quantity_Remaining__c) FROM Product2 WHERE Quantity_Remaining__c>0 AND IsActive = true GROUP BY Family]) { cht.add(new ChartData(String.ValueOf(ar.get('Family')), Integer.ValueOf(ar.get('expr0')))); } return cht; } public class ChartData { public String name {get;set;} public Decimal val {get;set;} public ChartData(String name, Decimal val){ this.name = name; this.val = val; } } }

Step 4:

TestDataFactory

/** * @name TestDataFactory * @description Contains methods to construct and/or validate commonly used records **/ public with sharing class TestDataFactory { /** * @name ConstructCollaborationGroup * @description **/ public static CollaborationGroup ConstructCollaborationGroup(){ //ToDo: Ensure this method returns a single Chatter CollaborationGroup // whose Name starts with 'TEST' followed by the INVENTORY_ANNOUNCEMENTS constant // and configured so anyone can join, see and post updates. CollaborationGroup ChatterGroup = new CollaborationGroup( Name = 'TEST'+Constants.INVENTORY_ANNOUNCEMENTS, CollaborationType = 'Public', IsArchived = false, IsAutoArchiveDisabled = true ); return ChatterGroup; } /** * @name CreateProducts * @description Constructs a list of Product2 records for unit tests **/ public static list<Product2> ConstructProducts(Integer cnt){ //ToDo: Ensure this method returns a list, of size cnt, of uniquely named Product2 records // with all the required fields populated // and IsActive = true // an Initial Inventory set to 10 // and iterating through the product family picklist values throughout the list. list<Product2> products=new list<Product2>(); list<Schema.PicklistEntry> pEntries = Constants.PRODUCT_FAMILY; Integer pVal = 0; for(Integer i=0;i<cnt;i++){ Product2 pd2=new Product2(); pd2.Name='Product-'+i; pd2.IsActive = true; pd2.Initial_Inventory__c = 10; if(pVal == 4){ pVal = 0; } pd2.Family = pEntries.get(pVal).getValue(); pVal++; products.add(pd2); } return products; } /** * @name CreatePricebookEntries * @description Constructs a list of PricebookEntry records for unit tests **/ public static list<PriceBookEntry> ConstructPricebookEntries(List<Product2> prods){ //ToDo: Ensure this method returns a corresponding list of PricebookEntries records // related to the provided Products // with all the required fields populated // and IsActive = true // and belonging to the standard Pricebook list<PriceBookEntry> entries=new list<PriceBookEntry>(); for(Product2 pd2: prods){ PriceBookEntry pbe=new PriceBookEntry(); pbe.isActive = true; pbe.UnitPrice = 100; pbe.Product2Id = pd2.id; pbe.PriceBook2Id = Constants.STANDARD_PRICEBOOK_ID; entries.add(pbe); } return entries; } /** * @name CreateAccounts * @description Constructs a list of Account records for unit tests **/ public static list<Account> ConstructAccounts(Integer cnt){ //ToDo: Ensure this method returns a list of size cnt of uniquely named Account records // with all of the required fields populated. list<Account> accounts=new list<Account>(); for(Integer i=0;i<cnt;i++){ Account ac=new Account(); ac.Name = 'Account'+i; accounts.add(ac); } return accounts; } /** * @name CreateContacts * @description Constructs a list of Contacxt records for unit tests **/ public static list<Contact> ConstructContacts(Integer cnt, List<Account> accts){ //ToDo: Ensure this method returns a list, of size cnt, of uniquely named Contact records // related to the provided Accounts // with all of the required fields populated. list<Contact> conts=new list<Contact>(); for(Integer i=0;i<cnt;i++){ contact con=new contact(); con.LastName = 'Contact'+i; con.AccountId = accts.get(i).id; conts.add(con); } return conts; } /** * @name CreateOrders * @description Constructs a list of Order records for unit tests **/ public static list<Order> ConstructOrders(Integer cnt, List<Account> accts){ //ToDo: Ensure this method returns a list of size cnt of uniquely named Order records // related to the provided Accounts // with all of the required fields populated. list<Order> orders=new list<Order>(); for(Integer i=0;i<cnt;i++){ Order ord=new Order(); ord.Name = 'Order'+i; ord.AccountId = accts.get(i).Id; ord.EffectiveDate = Date.Today(); ord.Status = Constants.DRAFT_ORDER_STATUS; ord.Pricebook2Id = Constants.STANDARD_PRICEBOOK_ID; orders.add(ord); } return orders; } /** * @name CreateOrderItems * @description Constructs a list of OrderItem records for unit tests **/ public static list<OrderItem> ConstructOrderItems(integer cnt, list<pricebookentry> pbes, list<order> ords){ //ToDo: Ensure this method returns a list of size cnt of OrderItem records // related to the provided Pricebook Entries // and related to the provided Orders // with all of the required fields populated. // Hint: Use the DEFAULT_ROWS constant for Quantity as it will be used in the next challenge list<OrderItem> orderItems=new list<OrderItem>(); for(Integer i=0;i<cnt;i++){ OrderItem oItem=new OrderItem(); oItem.OrderId = ords.get(i).Id; oItem.PriceBookEntryId = pbes.get(i).Id; oItem.Quantity = Constants.DEFAULT_ROWS; oItem.UnitPrice = 10; orderItems.add(oItem); } return orderItems; } /** * @name SetupTestData * @description Inserts accounts, contacts, Products, PricebookEntries, Orders, and OrderItems. **/ public static void InsertTestData(Integer cnt){ //ToDo: Ensure this method calls each of the construct methods // and inserts the results for use as test data. list<Account> accounts = ConstructAccounts(cnt); Insert accounts; list<Contact> contacts = ConstructContacts(cnt, accounts); insert contacts; list<Product2> prods= ConstructProducts(cnt); insert prods; list<PriceBookEntry> entries = ConstructPricebookEntries(prods); insert entries; list<Order> orders = ConstructOrders(cnt, accounts); insert orders; list<OrderItem> ordItems = ConstructOrderItems(cnt, entries, orders); insert ordItems; } }

Step 5

TestDataFactory

public static void VerifyQuantityOrdered(Product2 originalProduct, Product2 updatedProduct, Integer qtyOrdered){
        system.AssertEquals(updatedProduct.Quantity_Ordered__c,originalProduct.Quantity_Ordered__c  +qtyOrdered);
    }

Product2Test

@isTest (seeAllData=false) private class Product2Tests { /** * @name product2Extension_UnitTest * @description UnitTest for product2Extension **/ @isTest static void Product2Extension_UnitTest(){ PageReference pageRef = Page.Product2New; Test.setCurrentPage(pageRef); Product2 prod = new Product2(name='Test',isActive=true); ApexPages.StandardController stdcontroller = new ApexPages.StandardController(prod); Product2Extension ext = new Product2Extension(stdcontroller); System.assertEquals(Constants.DEFAULT_ROWS, ext.productsToInsert.size()); ext.addRows(); System.assertEquals(Constants.DEFAULT_ROWS * 2, ext.productsToInsert.size()); for (Integer i = 0; i < 5; i++) { Product2Extension.ProductWrapper wrapper = ext.productsToInsert[i]; Product2 testProduct = new Product2(); testProduct.Name = 'Test Product_' + i; testProduct.IsActive = true; testProduct.Initial_Inventory__c = 20; testProduct.Family = Constants.PRODUCT_FAMILY[0].getValue(); wrapper.productRecord = testProduct; PricebookEntry testEntry = new PricebookEntry(); testEntry.IsActive = true; testEntry.UnitPrice = 10; wrapper.pricebookEntryRecord = testEntry; } Test.startTest(); ext.save(); Test.stopTest(); ext.GetFamilyOptions(); ext.GetInventory(); List<Product2> createdProducts = [ SELECT Id FROM Product2 where Name LIKE :'Test Product%']; System.assertEquals(5, createdProducts.size()); } }

OrderTests


@isTest
public class OrderTests {
    @testSetup
    public static void SetupTestData(){
        
        TestDataFactory.InsertTestData(5);   
    }

    static testmethod void OrderUpdate_UnitTest (){
        test.startTest();
        Order rec = [select id, Status from Order limit 1];
        Product2 prod = [SELECT Family,Id,Name,Quantity_Ordered__c,Quantity_Remaining__c FROM Product2 limit 1];
        system.debug('kkk '+prod.Quantity_Ordered__c);
        rec.status = constants.ACTIVATED_ORDER_STATUS;
        Update rec;
        Product2 updatedprod = [SELECT Family,Id,Name,Quantity_Ordered__c,Quantity_Remaining__c FROM Product2 limit 1];
        
        system.debug('kkk '+updatedprod.Quantity_Ordered__c);
        TestDataFactory.VerifyQuantityOrdered(prod,updatedprod,constants.DEFAULT_ROWS);
         Test.stopTest();
    }
    

    
}

Step 6

Create a chatter group name Inventory Announcements with correct discription

AnnouncementQueueable


/** * @name AnnouncementQueueable * @description This class posts Chatter Announcements **/ public class AnnouncementQueueable implements System.Queueable{ public List<ConnectApi.AnnouncementInput> toPost; public AnnouncementQueueable(List<ConnectApi.AnnouncementInput> toPost){ this.toPost = toPost; } public void execute(QueueableContext context){ PostAnnouncements(toPost); } /** * @name postAnnouncements * @description This method is provided for you to facilitate the Super Badge **/ public static void PostAnnouncements(List<ConnectApi.AnnouncementInput> announcements){ while ( announcements.size() > 0 ){ if ( Limits.getDMLStatements() < Limits.getLimitDMLStatements() && !test.isRunningTest() ){ ConnectApi.AnnouncementInput a = announcements.remove(0); ConnectApi.Announcements.postAnnouncement('Internal', a); } else { AnnouncementQueueable announcementQueuable = new AnnouncementQueueable(announcements); // announcementQueuable.toPost = announcements; System.enqueueJob(announcementQueuable); break; } } if ( announcements.size() > 0 && !test.isRunningTest() ){ AnnouncementQueueable q = new AnnouncementQueueable(announcements); // q.toPost = announcements; System.enqueueJob(q); //ToDo: Enqueue the above instance of announcementQueueable } } }

Product2Helper Class

public class Product2Helper { /** * @name COLLABORATION_GROUP * @description List of CollaborationGroup used in both business and test logic **/ static List<CollaborationGroup> COLLABORATION_GROUP = [ SELECT Id FROM CollaborationGroup //WHERE Name = 'group name' WHERE Name = :Constants.INVENTORY_ANNOUNCEMENTS OR Name = :('TEST'+Constants.INVENTORY_ANNOUNCEMENTS) LIMIT 1 ]; /** * @name afterUpdate * @description called by product2 Trigger on After Update * @param List<Product2> newList * @param List<Product2> oldList **/ public static void AfterUpdate(List<Product2> newList){ // public static void AfterUpdate(List<Product2> newList, List<Product2> oldList){ List<Product2> needsAnnouncement = new List<Product2>(); Map<String, Inventory_Setting__mdt> records = new Map<String, Inventory_Setting__mdt>(); List<Inventory_Setting__mdt> inventorySettings = [SELECT Label, Low_Quantity_Alert__c FROM Inventory_Setting__mdt]; for(Inventory_Setting__mdt inventorySetting:inventorySettings){ records.put(inventorySetting.Label,inventorySetting); } for(Integer i=0;i<newList.size();i++){ Inventory_Setting__mdt inventorySetting = (Inventory_Setting__mdt)records.get(newList[i].Family); Integer alertQuantity = (Integer)inventorySetting.Low_Quantity_Alert__c; if( newList[i].Quantity_Remaining__c <= alertQuantity ){ needsAnnouncement.add(newList[i]); } /*(if( newList[i].Quantity_Remaining__c <= alertQuantity && oldList[i].Quantity_Remaining__c > alertQuantity){ needsAnnouncement.add(newList[i]); }*/ } PostAlerts(needsAnnouncement); //ToDo: Declare a List of Product2 records named needsAnnouncement //ToDo: Declare a Map of Strings to Inventory_Setting__mdt records //ToDo: Loop through a query of Inventory_Setting__mdt records and populate the Map with Name as the key //ToDo: Loop through the Products in newList // Use the corresponding Inventory Setting record to determine the correct Low Quantity Alert // If the Product's Quantity Remaining has been changed to less than the Low Quantity Alert // add it to the needsAnnouncement list //ToDo: Pass records to the postAlerts method } /** * @name postAlerts * @description called by product2 Trigger on After Update * @param List<Product2> productList **/ public static void PostAlerts(List<Product2> productList){ List<ConnectApi.AnnouncementInput> toPost = new List<ConnectApi.AnnouncementInput>(); for ( Product2 p : productList ){ ConnectApi.AnnouncementInput announcement = new ConnectApi.AnnouncementInput(); ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput(); List<ConnectApi.MessageSegmentInput> messageSegmentInput = new List<ConnectApi.MessageSegmentInput>(); ConnectApi.TextSegmentInput bodySegmentInput = new ConnectApi.TextSegmentInput(); bodySegmentInput.text = p.Name + ' ' + Constants.INVENTORY_LEVEL_LOW; messageSegmentInput.add(bodySegmentInput); messageBodyInput.messageSegments = messageSegmentInput; announcement.parentId = COLLABORATION_GROUP.get(0).Id; announcement.sendEmails = false; announcement.expirationDate = System.today()+1; announcement.body = messageBodyInput; toPost.add(announcement); // ToDo: Construct a new AnnouncementInput for the Chatter Group so that it: // expires in a day // does not notify users via email. // and has a text body that includes the name of the product followed by the INVENTORY_LEVEL_LOW constant } AnnouncementQueueable announcementQueuable = new AnnouncementQueueable(toPost); // announcementQueuable.toPost = toPost; Id jobId = System.enqueueJob(announcementQueuable); // ToDo: Create and enqueue an instance of the announcementQueuable class with the list of Products } }

Product Trigger

/** * @name product2Trigger * @description Trigger to notify staff of low levels of inventory **/ trigger product2Trigger on Product2 ( after update ) { Product2Helper.AfterUpdate((List<Product2>)Trigger.new); }

Step 7:

OrderExtension

/** * @name OrderExtension * @description This class is provided for you to facilitate the Super Badge **/ public class OrderExtension { public Order orderRecord {get;set;} public List<OrderItem> orderItemList {get;set;} public String selectedFamily {get;set;} public List<chartHelper.chartData> pieData {get;set;} public Decimal total {get;set;} public Map<Id,OrderItem> orderItemMap; ApexPages.StandardSetController standardSetController; public OrderExtension(ApexPages.StandardController standardController){ orderRecord = (Order)standardController.getRecord(); orderItemMap = new Map<id,OrderItem>(); if ( orderRecord.Id != null ){ orderRecord = queryOrderRecord(orderRecord.Id); } resetSsc(); total = 0; for (OrderItem oi : orderRecord.OrderItems) { orderItemMap.put(oi.Product2Id, oi); if (oi.Quantity > 0) { if (null == pieData) { pieData = new List<ChartHelper.ChartData>(); } pieData.add(new chartHelper.ChartData(oi.Product2.Name, oi.Quantity * oi.UnitPrice)); total += oi.UnitPrice * oi.Quantity; } } PopulateOrderItems(); } void resetSsc() { String query = 'SELECT Name, Product2.Family, Product2.Name, Product2Id, UnitPrice, Product2.Quantity_Remaining__c' + ' FROM PricebookEntry WHERE IsActive = TRUE'; if (selectedFamily != null && selectedFamily != Constants.SELECT_ONE) { query += ' AND Product2.Family = \'' + selectedFamily + '\''; } query += ' ORDER BY Name'; standardSetController = new ApexPages.StandardSetController(Database.getQueryLocator(query)); standardSetController.setPageSize(Constants.DEFAULT_ROWS); } //ToDo: Implement your own method to populate orderItemList // that you will call after pagination and/or family selection void PopulateOrderItems() { orderItemList = new List<OrderItem>(); for (SObject obj : standardSetController.getRecords()) { PricebookEntry pbe = (PricebookEntry)obj; if (orderItemMap.containsKey(pbe.Product2Id)) { orderItemList.add(orderItemMap.get(pbe.Product2Id)); } else { orderItemList.add(new OrderItem( PricebookEntryId=pbe.Id, Product2Id=pbe.Product2Id, UnitPrice=pbe.UnitPrice, Quantity=0, Product2=pbe.Product2 )); } } } /** * @name OnFieldChange * @description **/ public void OnFieldChange(){ //ToDo: Implement logic to store the values changed on the page for (OrderItem oi : orderItemList) { orderItemMap.put(oi.Product2Id, oi); } // and populate pieData pieData = null; total = 0; for (OrderItem oi : orderItemMap.values()) { if (oi.Quantity > 0) { if (null == pieData) { pieData = new List<chartHelper.ChartData>(); } pieData.add(new chartHelper.ChartData(oi.Product2.Name, oi.Quantity * oi.UnitPrice)); // and populate total total += oi.UnitPrice * oi.Quantity; } } } /** * @name SelectFamily * @description **/ public void SelectFamily(){ //ToDo: Implement logic to filter based on the selected product family resetSsc(); PopulateOrderItems(); } /** * @name Save * @description **/ public void Save(){ //ToDo: Implement logic to save the Order and populated OrderItems System.Savepoint sp = Database.setSavepoint(); try { if (null == orderRecord.Pricebook2Id) { orderRecord.Pricebook2Id = Constants.STANDARD_PRICEBOOK_ID; } upsert orderRecord; List<OrderItem> orderItemsToUpsert = new List<OrderItem>(); List<OrderItem> orderItemsToDelete = new List<OrderItem>(); for (OrderItem oi : orderItemList) { if (oi.Quantity > 0) { if (null == oi.OrderId) { oi.OrderId = orderRecord.Id; } orderItemsToUpsert.add(oi); } else if (oi.Id != null) { orderItemsToDelete.add(oi); } } upsert orderItemsToUpsert; delete orderItemsToDelete; } catch (Exception e) { Database.rollback(sp); apexPages.addMessage(new ApexPages.message(ApexPages.Severity.INFO,Constants.ERROR_MESSAGE)); } } /** * @name First * @description **/ public void First(){ standardSetController.first(); PopulateOrderItems(); } /** * @name Next * @description **/ public void Next(){ standardSetController.next(); PopulateOrderItems(); } /** * @name Previous * @description **/ public void Previous(){ standardSetController.previous(); PopulateOrderItems(); } /** * @name Last * @description **/ public void Last(){ standardSetController.last(); PopulateOrderItems(); } /** * @name GetHasPrevious * @description **/ public Boolean GetHasPrevious(){ return standardSetController.getHasPrevious(); } /** * @name GetHasNext * @description **/ public Boolean GetHasNext(){ return standardSetController.getHasNext(); } /** * @name GetTotalPages * @description **/ public Integer GetTotalPages(){ return (Integer)Math.ceil(standardSetController.getResultSize() / (Decimal)Constants.DEFAULT_ROWS); } /** * @name GetPageNumber * @description **/ public Integer GetPageNumber(){ return standardSetController.getPageNumber(); } /** * @name GetFamilyOptions * @description **/ public List<SelectOption> GetFamilyOptions() { List<SelectOption> options = new List<SelectOption>{ new SelectOption(Constants.SELECT_ONE, Constants.SELECT_ONE) }; for (Schema.PicklistEntry ple : Constants.PRODUCT_FAMILY) { options.add(new SelectOption(ple.getValue(), ple.getLabel())); } return options; } /** * @name QueryOrderRecord * @description **/ public static Order QueryOrderRecord(Id orderId){ return [ SELECT Id, AccountId, EffectiveDate, Name, Status, Pricebook2Id, ( SELECT Id, OrderId, Quantity, UnitPrice, PricebookEntryId, Product2Id, Product2.Name, Product2.Family, Product2.Quantity_Remaining__c FROM OrderItems ) FROM Order WHERE Id = :orderId ]; } }

Step 8:

AnnouncementQueueable Class

/** * @name AnnouncementQueueable * @description This class posts Chatter Announcements **/ public class AnnouncementQueueable implements System.Queueable{ public List<ConnectApi.AnnouncementInput> toPost; public AnnouncementQueueable(List<ConnectApi.AnnouncementInput> toPost){ this.toPost = toPost; } public void execute(QueueableContext context){ PostAnnouncements(toPost); } /** * @name postAnnouncements * @description This method is provided for you to facilitate the Super Badge **/ public static void PostAnnouncements(List<ConnectApi.AnnouncementInput> announcements){ while ( announcements.size() > 0 ){ if ( Limits.getDMLStatements() < Limits.getLimitDMLStatements() && !test.isRunningTest() ){ ConnectApi.AnnouncementInput a = announcements.remove(0); ConnectApi.Announcements.postAnnouncement('Internal', a); } else { AnnouncementQueueable announcementQueuable = new AnnouncementQueueable(announcements); // announcementQueuable.toPost = announcements; //System.enqueueJob(announcementQueuable); break; } } if ( announcements.size() > 0 && !test.isRunningTest() ){ AnnouncementQueueable q = new AnnouncementQueueable(announcements); // q.toPost = announcements; System.enqueueJob(q); //ToDo: Enqueue the above instance of announcementQueueable } } }



Product2Extension

Public without sharing class Product2Extension {

public List<productWrapper> productsToInsert {get;set;}
public Product2Extension(ApexPages.StandardController controller){
        productsToInsert = new List<productWrapper>();          
        AddRows();   
        }
         
         
    //Get select list options//     
    public List<SelectOption> GetFamilyOptions() {
        List<SelectOption> options = new List<SelectOption>{
            new SelectOption(Constants.SELECT_ONE, Constants.SELECT_ONE)
        };
        for (Schema.PicklistEntry ple : Constants.PRODUCT_FAMILY) {
            options.add(new SelectOption(ple.getValue(), ple.getLabel()));
        }
        return options;
    }
    public void AddRows(){
        for ( Integer i=0; i<Constants.DEFAULT_ROWS; i++ ){
            productsToInsert.add( new ProductWrapper() );
        }
    }

    public List<ChartHelper.ChartData> GetInventory(){
        return ChartHelper.GetInventory();
    }

    public PageReference Save(){
        Savepoint sp = Database.setSavepoint();
        try {
            List<Product2> products = new List<Product2>();
            List<PricebookEntry> entries = new List<PricebookEntry>();
            
            for (ProductWrapper wrp : productsToInsert){
                if(null!=wrp.productRecord && null!=wrp.pricebookEntryRecord){
                    
                    if(null!=wrp.productRecord.Name && null!=wrp.productRecord.Family && constants.SELECT_ONE!=wrp.productRecord.Family
                       && null!=wrp.productRecord.Initial_Inventory__c && null!=wrp.pricebookEntryRecord.UnitPrice){
                        products.add(wrp.productRecord);
                        PricebookEntry entry=wrp.pricebookEntryRecord;
                        entry.IsActive=true;
                        entry.Pricebook2Id=constants.STANDARD_PRICEBOOK_ID;
                        entries.add(entry);   
                    }
                }
            }
            
            insert products;            
            for (integer itr=0; itr<entries.size();itr++){
                entries[itr].Product2Id=products[itr].id;
            }
            insert entries;
            //If successful clear the list and display an informational message
            apexPages.addMessage(new ApexPages.message(ApexPages.Severity.INFO,productsToInsert.size()+' Inserted'));
            productsToInsert.clear();   //Do not remove
            addRows();  //Do not remove
        } catch (Exception e){
            Database.rollback(sp);
            apexPages.addMessage(new ApexPages.message(ApexPages.Severity.ERROR,constants.ERROR_MESSAGE));
        }
        return null;
    }
    
    public class ProductWrapper{
        public product2 productRecord {get;set;}
        public pricebookEntry pricebookEntryRecord{get;set;}
        public productWrapper(){
            productRecord = new product2(Initial_Inventory__c =0);
            pricebookEntryRecord = new pricebookEntry(Unitprice=0.0);
        }
    }
}

Product2Helper


public class Product2Helper {

    /**
     * @name COLLABORATION_GROUP
     * @description List of CollaborationGroup used in both business and test logic
    **/
    static List<CollaborationGroup> COLLABORATION_GROUP = [
        SELECT Id
        FROM CollaborationGroup
        //WHERE Name = 'group name'
        WHERE Name = :Constants.INVENTORY_ANNOUNCEMENTS
        OR Name = :('TEST'+Constants.INVENTORY_ANNOUNCEMENTS)
        LIMIT 1
    ];

    /**
     * @name afterUpdate
     * @description called by product2 Trigger on After Update
     * @param List<Product2> newList
     * @param List<Product2> oldList
    **/
    public static void AfterUpdate(List<Product2> newList){
//        public static void AfterUpdate(List<Product2> newList, List<Product2> oldList){
        
        List<Product2> needsAnnouncement = new List<Product2>();
        
        Map<String, Inventory_Setting__mdt> records = 
            new Map<String, Inventory_Setting__mdt>();

        List<Inventory_Setting__mdt> inventorySettings = [SELECT Label, Low_Quantity_Alert__c FROM Inventory_Setting__mdt];
        for(Inventory_Setting__mdt inventorySetting:inventorySettings){
            records.put(inventorySetting.Label,inventorySetting);
        }
        
        for(Integer i=0;i<newList.size();i++){
            Inventory_Setting__mdt inventorySetting = 
                    (Inventory_Setting__mdt)records.get(newList[i].Family);
            Integer alertQuantity = (Integer)inventorySetting.Low_Quantity_Alert__c;
            
            if( newList[i].Quantity_Remaining__c <= alertQuantity ){
                   needsAnnouncement.add(newList[i]);
               }
            
            /*(if( newList[i].Quantity_Remaining__c <= alertQuantity && 
               oldList[i].Quantity_Remaining__c > alertQuantity){
                   needsAnnouncement.add(newList[i]);
               }*/
        }
        
        PostAlerts(needsAnnouncement);
        
        
        //ToDo: Declare a List of Product2 records named needsAnnouncement

        //ToDo: Declare a Map of Strings to Inventory_Setting__mdt records

        //ToDo: Loop through a query of Inventory_Setting__mdt records and populate the Map with Name as the key

        //ToDo: Loop through the Products in newList
        // Use the corresponding Inventory Setting record to determine the correct Low Quantity Alert
        // If the Product's Quantity Remaining has been changed to less than the Low Quantity Alert
        //      add it to the needsAnnouncement list

        //ToDo: Pass records to the postAlerts method
    }

    /**
     * @name postAlerts
     * @description called by product2 Trigger on After Update
     * @param List<Product2> productList
    **/
    public static void PostAlerts(List<Product2> productList){
        List<ConnectApi.AnnouncementInput> toPost = new List<ConnectApi.AnnouncementInput>();
        for ( Product2 p : productList ){
            
            ConnectApi.AnnouncementInput announcement = 
                new ConnectApi.AnnouncementInput();
            ConnectApi.MessageBodyInput messageBodyInput = 
                new ConnectApi.MessageBodyInput();
            
            List<ConnectApi.MessageSegmentInput> messageSegmentInput = 
                new List<ConnectApi.MessageSegmentInput>();
            
            ConnectApi.TextSegmentInput bodySegmentInput = new ConnectApi.TextSegmentInput();
            bodySegmentInput.text = p.Name + ' ' + Constants.INVENTORY_LEVEL_LOW;
            messageSegmentInput.add(bodySegmentInput);
            messageBodyInput.messageSegments = messageSegmentInput;
            announcement.parentId = COLLABORATION_GROUP.get(0).Id;
            announcement.sendEmails = false;
            announcement.expirationDate = System.today()+1;
            announcement.body = messageBodyInput;
            
            toPost.add(announcement);

            // ToDo: Construct a new AnnouncementInput for the Chatter Group so that it:
            // expires in a day
            // does not notify users via email.
            // and has a text body that includes the name of the product followed by the INVENTORY_LEVEL_LOW constant
        }
        AnnouncementQueueable announcementQueuable = new AnnouncementQueueable(toPost);
//        announcementQueuable.toPost = toPost;
        
        Id jobId = System.enqueueJob(announcementQueuable);
        
        // ToDo: Create and enqueue an instance of the announcementQueuable class with the list of Products
    }
}

TestDataFactory

/**
 * @name TestDataFactory
 * @description Contains methods to construct and/or validate commonly used records
**/
public with sharing class TestDataFactory {

    public static List<Product2> prods;
    public static List<PricebookEntry> entries;
    public static  List<Order> orders;
    public static List<OrderItem> orderItems;
    /**
     * @name ConstructCollaborationGroup
     * @description
    **/
    public static CollaborationGroup ConstructCollaborationGroup(){
        //ToDo: Ensure this method returns a single Chatter CollaborationGroup
        //    whose Name starts with 'TEST' followed by the INVENTORY_ANNOUNCEMENTS constant
        //    and configured so anyone can join, see and post updates.
        CollaborationGroup ChatterGroup = new CollaborationGroup(
              Name = 'TEST'+Constants.INVENTORY_ANNOUNCEMENTS,  
              CollaborationType = 'Public',
              IsArchived = false,
              IsAutoArchiveDisabled = true
        );
        return ChatterGroup;
    }

    /**
     * @name CreateProducts
     * @description Constructs a list of Product2 records for unit tests
    **/
    public static list<Product2> ConstructProducts(Integer cnt){
        //ToDo: Ensure this method returns a list, of size cnt, of uniquely named Product2 records
        //  with all the required fields populated
        //  and IsActive = true
        //  an Initial Inventory set to 10
        //  and iterating through the product family picklist values throughout the list.
        list<Product2> products=new list<Product2>();
        list<Schema.PicklistEntry> pEntries = Constants.PRODUCT_FAMILY;
        Integer pVal = 0;
        for(Integer i=0;i<cnt;i++){
            Product2 pd2=new Product2();
            pd2.Name='Product-'+i;
            pd2.IsActive = true;
            pd2.Initial_Inventory__c = 10;
            if(pVal == 4){
                pVal = 0;
            }
            pd2.Family = pEntries.get(pVal).getValue();
            pVal++;
            products.add(pd2);
        }
        return products;
    }

    /**
     * @name CreatePricebookEntries
     * @description Constructs a list of PricebookEntry records for unit tests
    **/
    public static list<PriceBookEntry> ConstructPricebookEntries(List<Product2> prods){
        //ToDo: Ensure this method returns a corresponding list of PricebookEntries records
        //  related to the provided Products
        //  with all the required fields populated
        //  and IsActive = true
        //  and belonging to the standard Pricebook
        list<PriceBookEntry> entries=new list<PriceBookEntry>();
        for(Product2 pd2: prods){
            PriceBookEntry pbe=new PriceBookEntry();
            pbe.isActive = true;
            pbe.UnitPrice = 100;
            pbe.Product2Id = pd2.id;
            pbe.PriceBook2Id = Constants.STANDARD_PRICEBOOK_ID;
            entries.add(pbe);
        }
        return entries;
    }

    /**
     * @name CreateAccounts
     * @description Constructs a list of Account records for unit tests
    **/
    public static list<Account> ConstructAccounts(Integer cnt){
        //ToDo: Ensure this method returns a list of size cnt of uniquely named Account records
        //  with all of the required fields populated.
        list<Account> accounts=new list<Account>();
        for(Integer i=0;i<cnt;i++){
            Account ac=new Account();
            ac.Name = 'Account'+i;
            accounts.add(ac);
        }
        return accounts;
    }

    /**
     * @name CreateContacts
     * @description Constructs a list of Contacxt records for unit tests
    **/
    public static list<Contact> ConstructContacts(Integer cnt, List<Account> accts){
        //ToDo: Ensure this method returns a list, of size cnt, of uniquely named Contact records
        //  related to the provided Accounts
        //  with all of the required fields populated.
        list<Contact> conts=new list<Contact>();
        for(Integer i=0;i<cnt;i++){
            contact con=new contact();
            con.LastName = 'Contact'+i;
            con.AccountId = accts.get(i).id;
            conts.add(con);
        }
        return conts;
    }

    /**
     * @name CreateOrders
     * @description Constructs a list of Order records for unit tests
    **/
    public static list<Order> ConstructOrders(Integer cnt, List<Account> accts){
        //ToDo: Ensure this method returns a list of size cnt of uniquely named Order records
        //  related to the provided Accounts
        //  with all of the required fields populated.
        list<Order> orders=new list<Order>();
        for(Integer i=0;i<cnt;i++){
            Order ord=new Order();
            ord.Name = 'Order'+i;
            ord.AccountId = accts.get(i).Id;
            ord.EffectiveDate = Date.Today();
            ord.Status = Constants.DRAFT_ORDER_STATUS;
            ord.Pricebook2Id = Constants.STANDARD_PRICEBOOK_ID;
            orders.add(ord);
            
        }
        return orders;
    }

    /**
     * @name CreateOrderItems
     * @description Constructs a list of OrderItem records for unit tests
    **/
    public static list<OrderItem> ConstructOrderItems(integer cnt, list<pricebookentry> pbes, list<order> ords){
        //ToDo: Ensure this method returns a list of size cnt of OrderItem records
        //  related to the provided Pricebook Entries
        //  and related to the provided Orders
        //  with all of the required fields populated.
        //  Hint: Use the DEFAULT_ROWS constant for Quantity as it will be used in the next challenge
        list<OrderItem> orderItems=new list<OrderItem>();
        
        for(Integer i=0;i<cnt;i++){
            OrderItem oItem=new OrderItem();
            oItem.OrderId = ords.get(i).Id;
            oItem.PriceBookEntryId = pbes.get(i).Id;
            oItem.Quantity = Constants.DEFAULT_ROWS;
            oItem.UnitPrice = (Decimal)10;
            orderItems.add(oItem);
        }
        return orderItems;
    }

    public static void VerifyQuantityOrdered(Product2 originalProduct, Product2 updatedProduct, Integer qtyOrdered){
        //updatedProduct.Quantity_Ordered__c ==originalProduct.Quantity_Ordered__c  +qtyOrdered;
        system.AssertEquals(updatedProduct.Quantity_Ordered__c,originalProduct.Quantity_Ordered__c  +qtyOrdered);
    }
    /**
     * @name SetupTestData
     * @description Inserts accounts, contacts, Products, PricebookEntries, Orders, and OrderItems.
    **/
    public static void InsertTestData(Integer cnt){
        //ToDo: Ensure this method calls each of the construct methods
        //  and inserts the results for use as test data.
         list<Account> accounts = ConstructAccounts(cnt);
        Insert accounts;
        
        list<Contact> contacts = ConstructContacts(cnt, accounts);
        insert contacts;
        
        prods= ConstructProducts(cnt);
        insert prods;
        
        entries = ConstructPricebookEntries(prods);
        insert entries;
        
        orders = ConstructOrders(cnt, accounts);
        insert orders;
        
        orderItems = ConstructOrderItems(cnt, entries, orders);
        insert orderItems;

    }

}

OrderHelper


public without sharing class OrderHelper {

    public static void AfterUpdate(List<Order> newList, List<Order> oldList){
        Set<Id> orderIds = new Set<Id>();
        for ( Integer i=0; i< newList.size(); i++ ){
            if ( newList[i].Status == Constants.ACTIVATED_ORDER_STATUS && oldList[i].Status == Constants.DRAFT_ORDER_STATUS ){
                orderIds.add(newList[i].Id);
            }
        }
        RollUpOrderItems(orderIds);
    }
    public static void RollUpOrderItems(Set<Id> activatedOrderIds){
        Map<Id, Product2> productMap = new Map<Id, Product2>(); 
        for(OrderItem orderLine : [SELECT Id, Product2Id, Product2.Quantity_Ordered__c, Quantity, Order.ActivatedDate
                                   FROM OrderItem WHERE OrderId IN : activatedOrderIds]){ 
              if(!productMap.containsKey(orderLine.Product2Id))
                  productMap.put(orderLine.Product2Id, new Product2(Id =orderLine.Product2Id, Quantity_Ordered__c=0)); 
        }
        
        for(AggregateResult ag : [SELECT Sum(Quantity), Product2Id FROM OrderItem WHERE Product2Id IN : productMap.keySet() Group By Product2Id]){
            Id product2Id = (Id)ag.get('Product2Id');
            Product2 prod = productMap.get(product2Id);
            prod.Quantity_Ordered__c = (Decimal)ag.get('expr0');
            productMap.put(product2Id , prod);
        }
        try { 
            if(productMap.values() != null && productMap.values().size() > 0){ 
                update productMap.values(); 
            } 
        }catch ( Exception e ){ 
            System.debug('#### Exception Executed : '+e.getStackTraceString()); 
        }
    }
    
}

OrderExtension

/**
* @name OrderExtension
* @description This class is provided for you to facilitate the Super Badge
**/
public class OrderExtension {
    
    public Order orderRecord {get;set;}
    public List<OrderItem> orderItemList {get;set;}
    public String selectedFamily {get;set;}
    public List<chartHelper.chartData> pieData {get;set;}
    public Decimal total {get;set;}
    public Map<Id,OrderItem> orderItemMap;
    ApexPages.StandardSetController standardSetController;
    public OrderExtension(ApexPages.StandardController standardController){
        orderRecord = (Order)standardController.getRecord();
        orderItemMap = new Map<id,OrderItem>();
        if ( orderRecord.Id != null ){
            orderRecord = queryOrderRecord(orderRecord.Id);
        }
        resetSsc();
        total = 0;
        for (OrderItem oi : orderRecord.OrderItems) {
            orderItemMap.put(oi.Product2Id, oi);
            if (oi.Quantity > 0) {
                if (null == pieData) {
                    pieData = new List<ChartHelper.ChartData>();
                }
                pieData.add(new chartHelper.ChartData(oi.Product2.Name, oi.Quantity * oi.UnitPrice));
                total += oi.UnitPrice * oi.Quantity;
            }
        }
        PopulateOrderItems();
    }
    void resetSsc() {
        String query = 'SELECT Name, Product2.Family, Product2.Name, Product2Id, UnitPrice, Product2.Quantity_Remaining__c'
            + '  FROM PricebookEntry WHERE IsActive = TRUE';
        if (selectedFamily != null && selectedFamily != Constants.SELECT_ONE) {
            query += ' AND Product2.Family = \'' + selectedFamily + '\'';
        }
        query += ' ORDER BY Name';
        standardSetController = new ApexPages.StandardSetController(Database.getQueryLocator(query));
        standardSetController.setPageSize(Constants.DEFAULT_ROWS);
    }
    //ToDo: Implement your own method to populate orderItemList
    //  that you will call after pagination and/or family selection
    void PopulateOrderItems() {
        orderItemList = new List<OrderItem>();
        for (SObject obj : standardSetController.getRecords()) {
            PricebookEntry pbe = (PricebookEntry)obj;
            
            if (orderItemMap.containsKey(pbe.Product2Id)) {
                orderItemList.add(orderItemMap.get(pbe.Product2Id));
            } else {
                orderItemList.add(new OrderItem(
                    PricebookEntryId=pbe.Id,
                    Product2Id=pbe.Product2Id,
                    UnitPrice=pbe.UnitPrice,
                    Quantity=0,
                    Product2=pbe.Product2
                ));
            }
        }
    }
    /**
* @name OnFieldChange
* @description
**/
    public void OnFieldChange(){
        //ToDo: Implement logic to store the values changed on the page
        for (OrderItem oi : orderItemList) {
            orderItemMap.put(oi.Product2Id, oi);
        }
        //      and populate pieData
        pieData = null;
        total = 0;
        for (OrderItem oi : orderItemMap.values()) {
            if (oi.Quantity > 0) {
                if (null == pieData) {
                    pieData = new List<chartHelper.ChartData>();
                }
                pieData.add(new chartHelper.ChartData(oi.Product2.Name, oi.Quantity * oi.UnitPrice));
                //      and populate total
                total += oi.UnitPrice * oi.Quantity;
            }
        }
    }
    /**
* @name SelectFamily
* @description
**/
    public void SelectFamily(){
        //ToDo: Implement logic to filter based on the selected product family
        resetSsc();
        PopulateOrderItems();
    }
    /**
* @name Save
* @description
**/
    public void Save(){
        //ToDo: Implement logic to save the Order and populated OrderItems
        System.Savepoint sp = Database.setSavepoint();
        try {
            if (null == orderRecord.Pricebook2Id) {
                orderRecord.Pricebook2Id = Constants.STANDARD_PRICEBOOK_ID;
            }
            upsert orderRecord;
            List<OrderItem> orderItemsToUpsert = new List<OrderItem>();
            List<OrderItem> orderItemsToDelete = new List<OrderItem>();
            for (OrderItem oi : orderItemList) {
                if (oi.Quantity > 0) {
                    if (null == oi.OrderId) {
                        oi.OrderId = orderRecord.Id;
                    }
                    orderItemsToUpsert.add(oi);
                } else if (oi.Id != null) {
                    orderItemsToDelete.add(oi);
                }
            }
            upsert orderItemsToUpsert;
            delete orderItemsToDelete;
        } catch (Exception e) {
            Database.rollback(sp);
            apexPages.addMessage(new ApexPages.message(ApexPages.Severity.INFO,Constants.ERROR_MESSAGE));
        }
    }
    /**
* @name First
* @description
**/
    public void First(){
        standardSetController.first();
        PopulateOrderItems();
    }
    /**
* @name Next
* @description
**/
    public void Next(){
        standardSetController.next();
        PopulateOrderItems();
    }
    
    
    /**
* @name Previous
* @description
**/
    public void Previous(){
        standardSetController.previous();
        PopulateOrderItems();
    }
    /**
* @name Last
* @description
**/
    public void Last(){
        standardSetController.last();
        PopulateOrderItems();
    }
    /**
* @name GetHasPrevious
* @description
**/
    public Boolean GetHasPrevious(){
        return standardSetController.getHasPrevious();
    }
    
    /**
* @name GetHasNext
* @description
**/
    public Boolean GetHasNext(){
        return standardSetController.getHasNext();
    }
    
    /**
* @name GetTotalPages
* @description
**/
    public Integer GetTotalPages(){
        return (Integer)Math.ceil(standardSetController.getResultSize() / (Decimal)Constants.DEFAULT_ROWS);
    }
    
    /**
* @name GetPageNumber
* @description
**/
    public Integer GetPageNumber(){
        return standardSetController.getPageNumber();
    }
    
    /**
* @name GetFamilyOptions
* @description
**/
    public List<SelectOption> GetFamilyOptions() {
        List<SelectOption> options = new List<SelectOption>{
            new SelectOption(Constants.SELECT_ONE, Constants.SELECT_ONE)
                };
                    
                    for (Schema.PicklistEntry ple : Constants.PRODUCT_FAMILY) {
                        options.add(new SelectOption(ple.getValue(), ple.getLabel()));
                    }
        return options;
    }
    
    /**
* @name QueryOrderRecord
* @description
**/
    public static Order QueryOrderRecord(Id orderId){
        return [
            SELECT Id, AccountId, EffectiveDate, Name, Status, Pricebook2Id,
            (
                SELECT Id, OrderId, Quantity, UnitPrice, PricebookEntryId, Product2Id,
                Product2.Name, Product2.Family, Product2.Quantity_Remaining__c
                FROM OrderItems
            )
            FROM Order
            WHERE Id = :orderId
        ];
    }
}

Happy Coding !!!