• Dan Norfleet
  • NEWBIE
  • 145 Points
  • Member since 2017

  • Chatter
    Feed
  • 0
    Best Answers
  • 0
    Likes Received
  • 0
    Likes Given
  • 16
    Questions
  • 11
    Replies
I am creating a batch process to remove Chatter Posts after 180 Days.   Specifically the instructions are to remove the following:
  • Posts by group members & feed followers
  • Comments on posts
  • Attachments to posts of various media types such as pictures or PDF documents
To delete the FeedItem's I am selecting those > 180 days.  Then validate that the FeedItem originated either from a UserFeed or a CollaberationGroupFeed.  This seems to work fine for the posts.  

My question is how do I determine what attachments to remove?  I know you can have attachments on many objects, but the scope of mine are just the attachments for the chatter user / chatter group posts.

I can see when i create an attachment, it creates a FeedAttachment.  I can see that the ContentDocument.LastPublishedVersionId = FeedAttachment.RecordId.  How can I tell if the attachment is for a Chatter Group or Chatter User so i can issue the delete on the ContentDocument?

I appreciate any help you can provide.

Dan
URL to our site is something like "WWW.ABC.com"  Our users are able to enter "ABC.com" (not quite sure how - if that is done w/in Salesforce or from the Domain provider).  Our support team uses Service Now and has provided a link to Service Now (ie WWW.SERVICENOW.com).  Our business would like users to be able to enter "ABC.com/support" and take them directly to the servicenow URL. 

I have created a visual force page that i essentially calls the WWW.SERVICENOW.com URL when the user enters "WWW.ABC.com/support" it automatically take them to the Service Now page.  Did this by mapping the new support visual force page to the community site setup.

Business wants user to be able to enter "ABC.com/support" and NOT enter the beginning WWW.  Can this be done in Salesforce or does this need to be done by the domain provider?  If Salesforce, can you give me an idea of how?

Dan
I have a list of Id's defined as
          Set<ID> ExistingContactIds = new Set<ID>();
With that list, i use as input to build a second list
              list<account_access__c> ListMissingAcctContacts = 
                [SELECT account__c, contact__c, contact__r.name, contact__r.lastname, contact__r.firstname, contact__r.role__c
                FROM account_access__c 
                WHERE isdeleted = FALSE AND contact__r.role__c = 'Owner' AND account__c IN :MissingAcctsIds
                 ORDER BY account__c, contact__r.lastname, contact__r.firstname];

The first list may have 10 separate Id's and the second list may only have data for 7 of those Id's.  What is the easiest way to give me the values Id's from the first list that are NOT in the second list?  

Thanks for any help you can provide.

Dan
I have a need to interagate a field on a parent, and if it is TRUE, do an Update on a child object for those records or Insert if the records are not already there.  Specifically, adding a field to the Product object - boolean indicator.  If that boolean indicator is true for a given product, Insert 3 product Media records automatically (basically give URL's for the different image types for that product).  If records are already there, do update (so basically ability to do an UPSERT).

Can I use a process builder to achieve this?  If so, I do not see an option to insert records, only to update.  

Thanks for any suggestions.  If this can't be done via Process Builder, is Flow or Apex code using a trigger recommended path?

Dan
I have a process that will read through an order when it is updated and determine if there are duplicate sku's on the order.  If there are duplicates, I delete the duplicated row to leave only a single row for each Order / SKU.  My code is working, however, when I run a large number of orders through a single time, I get the Too Many SOQL query exception.  I know it is because I have a select within the For Loop.

I am having trouble separating the using the aggragate query results as paramaters in the second query.  Salesforce is new to me and not used to this restriction.  Can someone look at my code and suggest the best way to accomplish eliminating the Select from the For loop?

Following is my current code, I appreciate any suggestions.

Main ask, how do I use all the rows returned from the Aggragate results in ccOrders as parm values in the OrdItemDelTemp Select Statement?

Dan

/******************************************************************************
 * Determine if any of the orders have duplicate Items.
 * @param List<Id> orderIds 
 */
    public static void checkDuplicateOrderItems( List<Id> orderIds )
    {
        // Identify Orders within the list that have duplicate items - they will have a count > 1
        AggregateResult[] ccOrders =
             [SELECT ccrz__order__c ord_id
                     ,ccrz__product__r.ccrz__sku__c ord_sku
                     ,ccrz__orderlinetype__c ord_sku_type
                     ,ccrz__category__r.ccrz__categoryid__c ord_sku_catid
                     ,ccrz__category__r.name ord_sku_catname
                     ,COUNT(Id) dupitemcnt
              FROM ccrz__E_OrderItem__c 
              WHERE ccrz__Order__r.Id IN :orderIds 
              GROUP BY ccrz__order__c
                          ,ccrz__product__r.ccrz__sku__c
                          ,ccrz__orderlinetype__c
                          ,ccrz__category__r.ccrz__categoryid__c
                          ,ccrz__category__r.name
              HAVING Count(Id) > 1];

        // Exit Method loop if there are no duplicates in the orders being processed
        if( ccOrders.isEmpty() ) return;

        // Define list to store duplicates to be deleted.  
        List<ccrz__E_OrderItem__c> OrdItemDel = new List<ccrz__E_OrderItem__c>();
        ccrz__E_OrderItem__c OrdItemDelTemp = new ccrz__E_OrderItem__c();
        
        // For each duplicate identified in the order, add the duplicate rows to a list to delete.
        // The rows are deleted after the FOR loop. 
        for (AggregateResult ar : ccOrders)  {
             // using the duplicate and reading all but the last occurence of the sku into the list.  So
             // if a sku is on an order 2 times, it will only take the first occurrence and delete it.
             // The query will keep the order item with the highest quantity (if the quantity ordered is
             // different.
             OrdItemDelTemp =
                [SELECT id,
                          ccrz__product__r.ccrz__sku__c,
                          ccrz__orderlinetype__c,
                          ccrz__category__r.ccrz__categoryid__c,
                          ccrz__category__r.name,
                          ccrz__quantity__c
                 FROM ccrz__E_OrderItem__c
                 WHERE ccrz__order__c = :((ID)ar.get('ord_id'))
                   AND ccrz__product__r.ccrz__sku__c = :((String)ar.get('ord_sku'))
                         AND ccrz__orderlinetype__c = :((String)ar.get('ord_sku_type'))
                             AND ccrz__category__r.ccrz__categoryid__c = :((String)ar.get('ord_sku_catid'))
                         ORDER BY ccrz__quantity__c
                         LIMIT :((Integer)ar.get('dupitemcnt')) - 1];
                         
                         OrdItemDel.add(OrdItemDelTemp);
        }
        System.debug('**** DUPLICATE ORDER ITEMS TO DELETE:  '+ OrdItemDel);
        try 
            {
                            delete OrdItemDel;
            } 
            catch (Exception e) 
            {
                          System.debug('cc_HDPC_TriggerOrder.checkDuplicateOrderItems - ERROR DELETING DUPLICATE ORDER ITEMS - MSG = ' + e.getMessage().substring(1,255));
            }

        System.debug( 'EXIT: TriggerCC_HDPC_Order.checkDuplicateOrderItems()' );
    }
}

 
I am new to Apex Coding and have a hurdle people with more experience may be able to help me overcome.  When an update to an order is triggered, it is performing an Apex Class that does sharing and some other things.  I am attempting to add a method to this Class that will identify duplicate orders item combinations and delete them from the order.

In the following code I am able to display that the "AggregateResult[] ccOrders" query is returning the correct information.  I would like to take those variables and load them into a list where I can delete the records.  I am getting "Error: Compile Error: Invalid bind expression type of APEX_OBJECT for column of type Id at line ......" when i try to compile the class with "List<ccrz__E_OrderItem__c> OrdItemDel" query.  I have displayed the values that are used in the WHERE clause and those values are correct.

Question 1:  Is this the best way to delete those extra Order Item records that are duplicates (I have identified them successfully now want to delete them).
Question 2:  Can someone suggest what would fix the compile issue.
Question 3:  Once the query works and loads the rows to the list, i was planning to use the delete statement with exception processing around it, if you have suggestion on that, it is much appreciated.

The current code for the method in question is listed below.

Dan


/******************************************************************************
 * Determine if any of the orders have duplicate Items.
 * @param List<Id> orderIds 
 */
    public static void checkDuplicateOrderItems( List<Id> orderIds )
    {
        if( orderIds.isEmpty() ) return;

        // Identify Orders within the list that have duplicate items
                AggregateResult[] ccOrders =
             [SELECT ccrz__order__c ord_id
                              ,ccrz__product__r.ccrz__sku__c ord_sku
                              ,ccrz__orderlinetype__c ord_sku_type
                              ,ccrz__category__r.ccrz__categoryid__c ord_sku_catid
                              ,ccrz__category__r.name ord_sku_catname
                              ,COUNT(Id) dupitemcnt
                        FROM ccrz__E_OrderItem__c 
                        WHERE ccrz__Order__r.Id IN :orderIds 
                        GROUP BY ccrz__order__c
                                    ,ccrz__product__r.ccrz__sku__c
                                    ,ccrz__orderlinetype__c
                                    ,ccrz__category__r.ccrz__categoryid__c
                                    ,ccrz__category__r.name
                        HAVING Count(Id) > 1];

                 // Exit Method loop if there are no duplicates in the orders being processed
        if( ccOrders.isEmpty() ) return;
        
                for (AggregateResult ar : ccOrders)  {
                    Object v_ord_id = ar.get('ord_id');
                    Object v_ord_sku = ar.get('ord_sku');
                    Object v_ord_sku_type = ar.get('ord_sku_type');
                    Object v_ord_sku_catid = ar.get('ord_sku_catid');
                    Object v_ord_sku_catname = ar.get('ord_sku_catname');
                    Integer v_dupitemcnt = (Integer)ar.get('dupitemcnt');
                    System.debug('-------- DUPLICATE ORDER SKU -----------');
                    System.debug('ord_id = ' + v_ord_id);
                    System.debug('ord_sku   = ' + v_ord_sku);
                    System.debug('ord_sku_type = ' + v_ord_sku_type);
                    System.debug('ord_sku_catid = ' + v_ord_sku_catid);
                    System.debug('ord_sku_catname = ' + v_ord_sku_catname);
                    System.debug('dupitemcnt = ' + v_dupitemcnt);
                    
                    List<ccrz__E_OrderItem__c> OrdItemDel =
                        [SELECT ID 
                                   ,ccrz__product__r.ccrz__sku__c
                                   ,ccrz__orderlinetype__c
                                   ,ccrz__category__r.ccrz__categoryid__c
                                   ,ccrz__category__r.name
                                   ,ccrz__quantity__c
                         FROM ccrz__E_OrderItem__c
                         WHERE ccrz__order__c = :v_ord_id
                             AND ccrz__product__r.ccrz__sku__c = :v_ord_sku
                             AND ccrz__orderlinetype__c = :v_ord_sku_type
                             AND ccrz__category__r.ccrz__categoryid__c = :v_ord_sku_catid
                         ORDER BY ccrz__quantity__c DESC
                         LIMIT :v_dupitemcnt - 1];
                }
    }
}
I want to see debug statements for a specific Apex Class only - how do I do this?  I have went out to debug logs and set debug up on that specific class, however, the logs listed are only by user.  Am i overlooking something - or is there a different place to see the logs set up for the class?

I have also attemped to set up a debug level that has "NONE" for all except Apex Code (which I have set to "DEBUG") - however I get so many logs for the user when I have this set and have no way to quickly find the specific one that I would be looking for with the debug statements.

Any help / advice on how to quickly see the debug statements for a given user or apex class would be much appreciated.  I must be doing something incorrectly to get so many logs.

Dan
We had Salesforce create a Health Assessment report for us.  Found that we had 6 Apex Classes / 2 Apex Triggers / 5 Visualforce Pages that were using old versions of API's.  Within the "New Code using old API" section we had 37 Apex Classes / 2 Apex Triggers / 4 Visualforce Pages using old versions of API's.

We are new to this process so any guidance / answers / suggestions would be much appreciated for the following questions:
What are others doing to keep API's as current as possible (would it be a good practice to set time aside once a year to make sure all Classes / Triggers / Pages are using the most current version of API's)?  We are requiring when code is updated to upgrade to latest API, but sometimes code doesn't change often.
Can someone give a guess or rough estimate the amount of time it would take 2 people to go through process of upgrading to the latest versions of API's?  (ie so many hours per change or estimate of 40/80/120 ... hours)
What is the recommended process for Apex Classes to upgrade to the latest API version.
What is the recommended process for Apex Triggers to upgrade to the latest API version.
What is the recommended process for Visualforce Pages to upgrade to the latest API version.
Thanks for any guidance you can provide.  Trying to keep our environment as stable / healthy as possible.

Dan
We are trying to add logic to prevent duplicate items on orders.  It was suggested we use a Duplicate / Matching rule on the order item object.  Unfortunately the Visual Force page we have where the user enters items does not seem to use the Duplicate / Matching rule because we are still able to enter duplicates with no message or error.

Is there something that needs to be done to enable the Visual Force page to utilize the rules?

Dan
We need to create a process that loops through an order once it has been submitted and determine if it has duplicate items.  If it does, we would delete the duplicate from the order automatically.  We are still learning from the tools available. When reading through the order items, we will match on the SKU / Category - if there is more than 1 occurrence within that order, we wish to delete the last occurence.

Haven't figured out how to do this yet in Work Flow / Process Builder.  Wondered if someone with more experience on the various options can give the best practice solution?

Thanks 

Dan
We use DataLoader to load orders into our system.  we have ran into issue when we load high volumes (more than around 10,000 at a time) that Dataloader times out without any indication.  There are no errors in the error file.  Is there a way to configure to send an e-mail if it times out?  

We are not using a batch process at this time, just the dataloader app supplied with SalesForce.

Thanks for any suggestions.

Dan
I am trying to add a visual force page to my salesforce community page, and it worked fine in sandbox. i was able to drag the visual force component onto my community in the builder. but now in my dev environment that component does not exist. any ideas?  Does NOT give me the Visualforce Page option from within Community Builder.  Guessing this is a permissions issue of some sort.  My profile is system admin within each of the sandboxes.

Suggestions please :)

Dan
This is my first Apex Class.  I have tried to imlement the Batchable Class given by SalesForce and have the current logic working for a set of records when i execute from Developer's Console using the following:
     Batch_OrderStatus b = new Batch_OrderStatus();
     database.executebatch(b);

Records are updated successfully.  However, will only execute the limit i enter in the query.  I want the code to execute over and over until all records within the Select driver are exhausted.   It has been suggested to move the two Select Count queries into a map and not have them within the FOR loop.  I will try to make that change, but wanted to get clarification on a couple other questions.  I appreciate any direction the community can give me.

1)  What is best way to schedule a  batch process that is going to run daily?  Should I use the Schedule option within SalesForce within Apex Classes?  Add to batch job table?  Or is there a better way?
2)  How can I change my logic so it executes over and over until the initial query has been completed without getting Limit exceptions?  I know there is a better way to do this, but the current code just pulls the first 25 records out of the 300 that are returned in the query.

Following is my current code:

/*
 * @Name: Batch_OrderStatus
 * @Author: Dan Norfleet
 * Updates : February 2018 - Dan Norfleet - Created Apex Class
 */
global with sharing class Batch_OrderStatus implements Database.Batchable<sObject> {
    global final String query;
    public String statusSubmitted = 'Order Submitted';
    public String emailText = '\n ACTION           ORDER                  STATUS\n ------------------------------------------------';
    public List<ccrz__E_Order__c> ordList = new List<ccrz__E_Order__c>();
    public Integer total_orders_cnt = 0;
    public Integer update_received_cnt = 0;
    public Integer update_cancelled_cnt = 0;
    public String status_received = 'Received';
    public String status_order_received = 'Order Received';

    global Database.QueryLocator start(Database.BatchableContext BC) {
        String query = 'SELECT Id, name, ccrz__orderstatus__c FROM ccrz__E_Order__c WHERE ccrz__orderstatus__c = \'Order Submitted\'';
        query = query + 'limit 25';
        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext BC, List<sObject> scope) {
          // Loop through the list of orders
          for(sobject s : scope)
          { 
            ccrz__E_Order__c ord = (ccrz__E_Order__c)s;
            total_orders_cnt = total_orders_cnt + 1;
            String order_id = ord.Id;
            // For each order, see how many items are in 'Received' status
            integer ItemsReceived = 
                    [SELECT COUNT() 
                    FROM ccrz__E_OrderItem__c 
                    WHERE ccrz__orderitemstatus__c = :status_received
                    AND ccrz__order__c = :order_id];
              
            integer ItemsNOTReceived = 
                    [SELECT COUNT() 
                    FROM ccrz__E_OrderItem__c 
                    WHERE ccrz__orderitemstatus__c != :status_received
                    AND ccrz__order__c = :order_id];
          
            if (ItemsReceived > 0 && ItemsNOTReceived == 0)
                { 
                ord.ccrz__orderstatus__c = 'Order Received';
                update_received_cnt = update_received_cnt + 1;
                emailText = emailText + '\n Updated         ' + ord.Name + '      Order Received';
                }
            else if (ItemsReceived == 0 && ItemsNOTReceived == 0)
                { 
                ord.ccrz__orderstatus__c = 'Cancelled';
                update_cancelled_cnt = update_cancelled_cnt + 1;
                emailText = emailText + '\n Updated         ' + ord.Name + '      Cancelled';
                }
                        else
                                {
                emailText = emailText + '\n No Change     ' + ord.Name + '      Order Submitted';
                                }

          }
          update scope;
          
                  /* E-Mail */
                  //Send an email to the User after your batch completes
    System.debug('    emailText = ' + emailText);
                  Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
                  mail.ToAddresses  = new string[] {'batch.example@google.com'};
                  mail.setSubject('Apex Batch Job is done');
                  mail.setPlainTextBody(emailText);
                  Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
    }

    global void finish(Database.BatchableContext BC) {
          AsyncApexJob a = [SELECT Id,
                            Status,
                            NumberOfErrors,
                            JobItemsProcessed,
                            TotalJobItems,
                            CompletedDate,
                            ExtendedStatus,
                            ApexClass.name,
                            CreatedBy.Email,
                            CreatedBy.Name
                            FROM AsyncApexJob
                            WHERE Id = :BC.getJobId()];
  
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            mail.ToAddresses  = new string[] {a.CreatedBy.Email};
            mail.setSubject('Async Apex Batch Job Status' + a.ApexClass.name);
    
            string td1 = '"border:1px solid #e6e6e6; width=200px;"';
            string td2 = '"width=200px; border:1px solid #e6e6e6; background-color:#E44905; color:white; font-weight:bold;"';
            string tdHead = '"border:1px solid #e6e6e6; width=200px; color:white; background-color:#9370DB; font-weight:bold;"';
            string ExtendedStatus = '';
            if (a.ExtendedStatus != null)
                ExtendedStatus = a.ExtendedStatus;
            string tdErrors = td1;
            if (a.NumberOfErrors > 0)
                tdErrors = td2;
            string htmlBody = '<div style="border:2px solid #e6e6e6; border-radius:10px; "><p>Hi Team,</p><p><span style="color:brown; font-weight:bolder;">Hallmark Salesforce</span> completed running <b>Apex Batch Code.</p>'
                              + '<p>Results:</p>'
                              + '<center><table style="border:3px solid #e6e6e6; border-collapse:collapse;">'
                              + '<tr><td style=' + tdHead + '>Class Name</td><td style=' + tdHead + '>' + a.ApexClass.name + '</td></tr>'
                              + '<tr><td style=' + td1 + '>Completed Date</td><td style=' + td1 + '>' + a.CompletedDate + '</td></tr>'
                              + '<tr><td style=' + td1 + '>Status</td><td style=' + td1 + '>' + a.Status + '</td></tr>'
                              + '<tr><td style=' + td1 + '>Job Items Processed</td><td style=' + td1 + '>' + a.JobItemsProcessed + ' / ' + a.TotalJobItems + '</td></tr>'
                              + '<tr><td style=' + td1 + '>NumberOfErrors</td><td style=' + tdErrors + '>' + a.NumberOfErrors + '</td></tr>'
                              + '<tr><td style=' + td1 + '>Extended Status</td><td style=' + td1 + '>' + ExtendedStatus + '</td></tr>'
                              + '<tr><td style=' + tdHead + '>Created By</td><td style=' + tdHead + '>' + a.CreatedBy.Name + ' (' + a.CreatedBy.Email + ')</td></tr>'
                              + '</table></center>'
                              + '<p><span style="font-family:"Courier New", Courier, monospace; color:#e6e6e6; font-weight:bold; font-size:larger;">Hallmark Salesforce Admin Team</span></p></div>';
    
            mail.setHtmlBody(htmlBody);
            List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>();
            mails.add(mail);
            Messaging.sendEmail(mails);
    }
}
Just want to double check with the experts of the community.  If i add a new picklist value (for example add new 'Cancel' status to an Order), will this cause contention with active users?  For example if someone is within an order at the same time I add new value to Picklist - could it effect the user's experience?  I didn't see anything on Salesforce that indicated it will, but thought I would check.  We can schedule these changes in off-hours, but just want to make sure clear on our options.

Thanks for the help.

Dan
I am new to SalesForce.  I have created my first Apex Class.  The goal of the class is to read through all ccrz__E_Order__c that are in 'Order Submitted' status.  For each order, read the items.  If all items are currently in 'Received' status, then update the status on the order to 'Order Received'.  I want this to be a batch process ran daily.

Following questions being a first time creator of an Apex Class.
  • Is the best way to trigger this process to run by going into Apex Class and Schedule Apex?  Or is there a more appropriate way to schedule?
  • When testing, I have used Developers Console and executed the class.  However, I run into limits.  Even when I have scheduled in our test environments, i have hit limits.  There are only a couple hundred orders in this status but it hits Too many SOQL queries system limit exception.  Is there a way to test the entire process w/o exceeding these limits?  Are they the same limits when going to production?  When testing I enter specific orders (you'll see commented out line below in the code), but when i read all orders i am hitting these limits.
  • Is there a system log that i can write totals too?  As you can see on the code below i have written beginning / ending / total information into the debug log.  But when going to production, i will comment these out - is there a traditional way to look at this kind of information in a salesforce log.  
I am including the code i have created and appreciate any feedback since this is my first attempt.

Thanks in advance for any advise.  

Dan

/*
 * @Name: Batch_OrderStatus
 * @Author: Dan Norfleet
 * Updates : 02/12/2018 - Dan Norfleet - Created Apex Class
 */
    global class Batch_OrderStatus Implements Schedulable
    {
      global void execute(SchedulableContext sc)
      {
        execute();
      }

      global void execute() 
      {
          System.debug('Batch_OrderStatus BEGIN');
          //  Create a list of Orders in status of 'Order Submitted'
          List<ccrz__E_Order__c> orders = 
                [SELECT Id, Name, ccrz__orderstatus__c 
                FROM ccrz__E_Order__c 
                WHERE ccrz__orderstatus__c = 'Order Submitted'];
//                WHERE ccrz__orderstatus__c = 'Order Submitted' AND Name = 'O-0000112447'];
          Integer total_orders_cnt = 0;
          Integer update_cnt = 0;
          Integer notupdated_cnt = 0;
          String status_received = 'Received';
          String status_order_received = 'Order Received';
          
          // Loop through the list of orders
          for(ccrz__E_Order__c ord : orders)
          { 
            total_orders_cnt = total_orders_cnt + 1;
            String order_id = ord.Id;
            // For each order, see how many items are in 'Received' status
            integer ItemsReceived = 
                    [SELECT COUNT() 
                    FROM ccrz__E_OrderItem__c 
                    WHERE ccrz__orderitemstatus__c = :status_received
                    AND ccrz__order__c = :order_id];
          
            integer ItemsNOTReceived = 
                    [SELECT COUNT() 
                    FROM ccrz__E_OrderItem__c 
                    WHERE ccrz__orderitemstatus__c != :status_received
                    AND ccrz__order__c = :order_id];
          
            if (ItemsReceived > 0)
                    { 
                if (ItemsNOTReceived == 0)
                        { 
                        ord.ccrz__orderstatus__c = status_order_received;
                        System.debug('    UPDATE Order - ID = ' + order_id + ' / Order Name = ' + ord.Name + ' / Items Received = ' + ItemsReceived + ' / ItemsNOTReceived = ' + ItemsNOTReceived);
                        update_cnt = update_cnt + 1;
                        update orders;
                        }
                    else
                        {
                        notupdated_cnt = notupdated_cnt + 1;
                        }
                    }
                
          }
          System.debug('TOTAL ORDERS IN SUBMITTED STATUS = ' + total_orders_cnt);
          System.debug('TOTAL UPDATED = ' + update_cnt);
          System.debug('TOTAL NOTUPDATED = ' + notupdated_cnt);
          System.debug('Batch_OrderStatus END');
      }
}
I am new to SalesForce.  Would like to know the best way to accomplish a task.  We have cc orders in status of 'Order Submitted' when all of the cc order items have been received.  Would like a one time daily process to read all cc orders that are in submitted status and if all items on the order have been received, change status of order to 'Received'.  Can Process Builder / Flow be used?  

Dan
I have a process that will read through an order when it is updated and determine if there are duplicate sku's on the order.  If there are duplicates, I delete the duplicated row to leave only a single row for each Order / SKU.  My code is working, however, when I run a large number of orders through a single time, I get the Too Many SOQL query exception.  I know it is because I have a select within the For Loop.

I am having trouble separating the using the aggragate query results as paramaters in the second query.  Salesforce is new to me and not used to this restriction.  Can someone look at my code and suggest the best way to accomplish eliminating the Select from the For loop?

Following is my current code, I appreciate any suggestions.

Main ask, how do I use all the rows returned from the Aggragate results in ccOrders as parm values in the OrdItemDelTemp Select Statement?

Dan

/******************************************************************************
 * Determine if any of the orders have duplicate Items.
 * @param List<Id> orderIds 
 */
    public static void checkDuplicateOrderItems( List<Id> orderIds )
    {
        // Identify Orders within the list that have duplicate items - they will have a count > 1
        AggregateResult[] ccOrders =
             [SELECT ccrz__order__c ord_id
                     ,ccrz__product__r.ccrz__sku__c ord_sku
                     ,ccrz__orderlinetype__c ord_sku_type
                     ,ccrz__category__r.ccrz__categoryid__c ord_sku_catid
                     ,ccrz__category__r.name ord_sku_catname
                     ,COUNT(Id) dupitemcnt
              FROM ccrz__E_OrderItem__c 
              WHERE ccrz__Order__r.Id IN :orderIds 
              GROUP BY ccrz__order__c
                          ,ccrz__product__r.ccrz__sku__c
                          ,ccrz__orderlinetype__c
                          ,ccrz__category__r.ccrz__categoryid__c
                          ,ccrz__category__r.name
              HAVING Count(Id) > 1];

        // Exit Method loop if there are no duplicates in the orders being processed
        if( ccOrders.isEmpty() ) return;

        // Define list to store duplicates to be deleted.  
        List<ccrz__E_OrderItem__c> OrdItemDel = new List<ccrz__E_OrderItem__c>();
        ccrz__E_OrderItem__c OrdItemDelTemp = new ccrz__E_OrderItem__c();
        
        // For each duplicate identified in the order, add the duplicate rows to a list to delete.
        // The rows are deleted after the FOR loop. 
        for (AggregateResult ar : ccOrders)  {
             // using the duplicate and reading all but the last occurence of the sku into the list.  So
             // if a sku is on an order 2 times, it will only take the first occurrence and delete it.
             // The query will keep the order item with the highest quantity (if the quantity ordered is
             // different.
             OrdItemDelTemp =
                [SELECT id,
                          ccrz__product__r.ccrz__sku__c,
                          ccrz__orderlinetype__c,
                          ccrz__category__r.ccrz__categoryid__c,
                          ccrz__category__r.name,
                          ccrz__quantity__c
                 FROM ccrz__E_OrderItem__c
                 WHERE ccrz__order__c = :((ID)ar.get('ord_id'))
                   AND ccrz__product__r.ccrz__sku__c = :((String)ar.get('ord_sku'))
                         AND ccrz__orderlinetype__c = :((String)ar.get('ord_sku_type'))
                             AND ccrz__category__r.ccrz__categoryid__c = :((String)ar.get('ord_sku_catid'))
                         ORDER BY ccrz__quantity__c
                         LIMIT :((Integer)ar.get('dupitemcnt')) - 1];
                         
                         OrdItemDel.add(OrdItemDelTemp);
        }
        System.debug('**** DUPLICATE ORDER ITEMS TO DELETE:  '+ OrdItemDel);
        try 
            {
                            delete OrdItemDel;
            } 
            catch (Exception e) 
            {
                          System.debug('cc_HDPC_TriggerOrder.checkDuplicateOrderItems - ERROR DELETING DUPLICATE ORDER ITEMS - MSG = ' + e.getMessage().substring(1,255));
            }

        System.debug( 'EXIT: TriggerCC_HDPC_Order.checkDuplicateOrderItems()' );
    }
}

 
I am new to Apex Coding and have a hurdle people with more experience may be able to help me overcome.  When an update to an order is triggered, it is performing an Apex Class that does sharing and some other things.  I am attempting to add a method to this Class that will identify duplicate orders item combinations and delete them from the order.

In the following code I am able to display that the "AggregateResult[] ccOrders" query is returning the correct information.  I would like to take those variables and load them into a list where I can delete the records.  I am getting "Error: Compile Error: Invalid bind expression type of APEX_OBJECT for column of type Id at line ......" when i try to compile the class with "List<ccrz__E_OrderItem__c> OrdItemDel" query.  I have displayed the values that are used in the WHERE clause and those values are correct.

Question 1:  Is this the best way to delete those extra Order Item records that are duplicates (I have identified them successfully now want to delete them).
Question 2:  Can someone suggest what would fix the compile issue.
Question 3:  Once the query works and loads the rows to the list, i was planning to use the delete statement with exception processing around it, if you have suggestion on that, it is much appreciated.

The current code for the method in question is listed below.

Dan


/******************************************************************************
 * Determine if any of the orders have duplicate Items.
 * @param List<Id> orderIds 
 */
    public static void checkDuplicateOrderItems( List<Id> orderIds )
    {
        if( orderIds.isEmpty() ) return;

        // Identify Orders within the list that have duplicate items
                AggregateResult[] ccOrders =
             [SELECT ccrz__order__c ord_id
                              ,ccrz__product__r.ccrz__sku__c ord_sku
                              ,ccrz__orderlinetype__c ord_sku_type
                              ,ccrz__category__r.ccrz__categoryid__c ord_sku_catid
                              ,ccrz__category__r.name ord_sku_catname
                              ,COUNT(Id) dupitemcnt
                        FROM ccrz__E_OrderItem__c 
                        WHERE ccrz__Order__r.Id IN :orderIds 
                        GROUP BY ccrz__order__c
                                    ,ccrz__product__r.ccrz__sku__c
                                    ,ccrz__orderlinetype__c
                                    ,ccrz__category__r.ccrz__categoryid__c
                                    ,ccrz__category__r.name
                        HAVING Count(Id) > 1];

                 // Exit Method loop if there are no duplicates in the orders being processed
        if( ccOrders.isEmpty() ) return;
        
                for (AggregateResult ar : ccOrders)  {
                    Object v_ord_id = ar.get('ord_id');
                    Object v_ord_sku = ar.get('ord_sku');
                    Object v_ord_sku_type = ar.get('ord_sku_type');
                    Object v_ord_sku_catid = ar.get('ord_sku_catid');
                    Object v_ord_sku_catname = ar.get('ord_sku_catname');
                    Integer v_dupitemcnt = (Integer)ar.get('dupitemcnt');
                    System.debug('-------- DUPLICATE ORDER SKU -----------');
                    System.debug('ord_id = ' + v_ord_id);
                    System.debug('ord_sku   = ' + v_ord_sku);
                    System.debug('ord_sku_type = ' + v_ord_sku_type);
                    System.debug('ord_sku_catid = ' + v_ord_sku_catid);
                    System.debug('ord_sku_catname = ' + v_ord_sku_catname);
                    System.debug('dupitemcnt = ' + v_dupitemcnt);
                    
                    List<ccrz__E_OrderItem__c> OrdItemDel =
                        [SELECT ID 
                                   ,ccrz__product__r.ccrz__sku__c
                                   ,ccrz__orderlinetype__c
                                   ,ccrz__category__r.ccrz__categoryid__c
                                   ,ccrz__category__r.name
                                   ,ccrz__quantity__c
                         FROM ccrz__E_OrderItem__c
                         WHERE ccrz__order__c = :v_ord_id
                             AND ccrz__product__r.ccrz__sku__c = :v_ord_sku
                             AND ccrz__orderlinetype__c = :v_ord_sku_type
                             AND ccrz__category__r.ccrz__categoryid__c = :v_ord_sku_catid
                         ORDER BY ccrz__quantity__c DESC
                         LIMIT :v_dupitemcnt - 1];
                }
    }
}
We are trying to add logic to prevent duplicate items on orders.  It was suggested we use a Duplicate / Matching rule on the order item object.  Unfortunately the Visual Force page we have where the user enters items does not seem to use the Duplicate / Matching rule because we are still able to enter duplicates with no message or error.

Is there something that needs to be done to enable the Visual Force page to utilize the rules?

Dan
We need to create a process that loops through an order once it has been submitted and determine if it has duplicate items.  If it does, we would delete the duplicate from the order automatically.  We are still learning from the tools available. When reading through the order items, we will match on the SKU / Category - if there is more than 1 occurrence within that order, we wish to delete the last occurence.

Haven't figured out how to do this yet in Work Flow / Process Builder.  Wondered if someone with more experience on the various options can give the best practice solution?

Thanks 

Dan
We use DataLoader to load orders into our system.  we have ran into issue when we load high volumes (more than around 10,000 at a time) that Dataloader times out without any indication.  There are no errors in the error file.  Is there a way to configure to send an e-mail if it times out?  

We are not using a batch process at this time, just the dataloader app supplied with SalesForce.

Thanks for any suggestions.

Dan
I am trying to add a visual force page to my salesforce community page, and it worked fine in sandbox. i was able to drag the visual force component onto my community in the builder. but now in my dev environment that component does not exist. any ideas?  Does NOT give me the Visualforce Page option from within Community Builder.  Guessing this is a permissions issue of some sort.  My profile is system admin within each of the sandboxes.

Suggestions please :)

Dan
This is my first Apex Class.  I have tried to imlement the Batchable Class given by SalesForce and have the current logic working for a set of records when i execute from Developer's Console using the following:
     Batch_OrderStatus b = new Batch_OrderStatus();
     database.executebatch(b);

Records are updated successfully.  However, will only execute the limit i enter in the query.  I want the code to execute over and over until all records within the Select driver are exhausted.   It has been suggested to move the two Select Count queries into a map and not have them within the FOR loop.  I will try to make that change, but wanted to get clarification on a couple other questions.  I appreciate any direction the community can give me.

1)  What is best way to schedule a  batch process that is going to run daily?  Should I use the Schedule option within SalesForce within Apex Classes?  Add to batch job table?  Or is there a better way?
2)  How can I change my logic so it executes over and over until the initial query has been completed without getting Limit exceptions?  I know there is a better way to do this, but the current code just pulls the first 25 records out of the 300 that are returned in the query.

Following is my current code:

/*
 * @Name: Batch_OrderStatus
 * @Author: Dan Norfleet
 * Updates : February 2018 - Dan Norfleet - Created Apex Class
 */
global with sharing class Batch_OrderStatus implements Database.Batchable<sObject> {
    global final String query;
    public String statusSubmitted = 'Order Submitted';
    public String emailText = '\n ACTION           ORDER                  STATUS\n ------------------------------------------------';
    public List<ccrz__E_Order__c> ordList = new List<ccrz__E_Order__c>();
    public Integer total_orders_cnt = 0;
    public Integer update_received_cnt = 0;
    public Integer update_cancelled_cnt = 0;
    public String status_received = 'Received';
    public String status_order_received = 'Order Received';

    global Database.QueryLocator start(Database.BatchableContext BC) {
        String query = 'SELECT Id, name, ccrz__orderstatus__c FROM ccrz__E_Order__c WHERE ccrz__orderstatus__c = \'Order Submitted\'';
        query = query + 'limit 25';
        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext BC, List<sObject> scope) {
          // Loop through the list of orders
          for(sobject s : scope)
          { 
            ccrz__E_Order__c ord = (ccrz__E_Order__c)s;
            total_orders_cnt = total_orders_cnt + 1;
            String order_id = ord.Id;
            // For each order, see how many items are in 'Received' status
            integer ItemsReceived = 
                    [SELECT COUNT() 
                    FROM ccrz__E_OrderItem__c 
                    WHERE ccrz__orderitemstatus__c = :status_received
                    AND ccrz__order__c = :order_id];
              
            integer ItemsNOTReceived = 
                    [SELECT COUNT() 
                    FROM ccrz__E_OrderItem__c 
                    WHERE ccrz__orderitemstatus__c != :status_received
                    AND ccrz__order__c = :order_id];
          
            if (ItemsReceived > 0 && ItemsNOTReceived == 0)
                { 
                ord.ccrz__orderstatus__c = 'Order Received';
                update_received_cnt = update_received_cnt + 1;
                emailText = emailText + '\n Updated         ' + ord.Name + '      Order Received';
                }
            else if (ItemsReceived == 0 && ItemsNOTReceived == 0)
                { 
                ord.ccrz__orderstatus__c = 'Cancelled';
                update_cancelled_cnt = update_cancelled_cnt + 1;
                emailText = emailText + '\n Updated         ' + ord.Name + '      Cancelled';
                }
                        else
                                {
                emailText = emailText + '\n No Change     ' + ord.Name + '      Order Submitted';
                                }

          }
          update scope;
          
                  /* E-Mail */
                  //Send an email to the User after your batch completes
    System.debug('    emailText = ' + emailText);
                  Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
                  mail.ToAddresses  = new string[] {'batch.example@google.com'};
                  mail.setSubject('Apex Batch Job is done');
                  mail.setPlainTextBody(emailText);
                  Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
    }

    global void finish(Database.BatchableContext BC) {
          AsyncApexJob a = [SELECT Id,
                            Status,
                            NumberOfErrors,
                            JobItemsProcessed,
                            TotalJobItems,
                            CompletedDate,
                            ExtendedStatus,
                            ApexClass.name,
                            CreatedBy.Email,
                            CreatedBy.Name
                            FROM AsyncApexJob
                            WHERE Id = :BC.getJobId()];
  
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            mail.ToAddresses  = new string[] {a.CreatedBy.Email};
            mail.setSubject('Async Apex Batch Job Status' + a.ApexClass.name);
    
            string td1 = '"border:1px solid #e6e6e6; width=200px;"';
            string td2 = '"width=200px; border:1px solid #e6e6e6; background-color:#E44905; color:white; font-weight:bold;"';
            string tdHead = '"border:1px solid #e6e6e6; width=200px; color:white; background-color:#9370DB; font-weight:bold;"';
            string ExtendedStatus = '';
            if (a.ExtendedStatus != null)
                ExtendedStatus = a.ExtendedStatus;
            string tdErrors = td1;
            if (a.NumberOfErrors > 0)
                tdErrors = td2;
            string htmlBody = '<div style="border:2px solid #e6e6e6; border-radius:10px; "><p>Hi Team,</p><p><span style="color:brown; font-weight:bolder;">Hallmark Salesforce</span> completed running <b>Apex Batch Code.</p>'
                              + '<p>Results:</p>'
                              + '<center><table style="border:3px solid #e6e6e6; border-collapse:collapse;">'
                              + '<tr><td style=' + tdHead + '>Class Name</td><td style=' + tdHead + '>' + a.ApexClass.name + '</td></tr>'
                              + '<tr><td style=' + td1 + '>Completed Date</td><td style=' + td1 + '>' + a.CompletedDate + '</td></tr>'
                              + '<tr><td style=' + td1 + '>Status</td><td style=' + td1 + '>' + a.Status + '</td></tr>'
                              + '<tr><td style=' + td1 + '>Job Items Processed</td><td style=' + td1 + '>' + a.JobItemsProcessed + ' / ' + a.TotalJobItems + '</td></tr>'
                              + '<tr><td style=' + td1 + '>NumberOfErrors</td><td style=' + tdErrors + '>' + a.NumberOfErrors + '</td></tr>'
                              + '<tr><td style=' + td1 + '>Extended Status</td><td style=' + td1 + '>' + ExtendedStatus + '</td></tr>'
                              + '<tr><td style=' + tdHead + '>Created By</td><td style=' + tdHead + '>' + a.CreatedBy.Name + ' (' + a.CreatedBy.Email + ')</td></tr>'
                              + '</table></center>'
                              + '<p><span style="font-family:"Courier New", Courier, monospace; color:#e6e6e6; font-weight:bold; font-size:larger;">Hallmark Salesforce Admin Team</span></p></div>';
    
            mail.setHtmlBody(htmlBody);
            List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>();
            mails.add(mail);
            Messaging.sendEmail(mails);
    }
}
Just want to double check with the experts of the community.  If i add a new picklist value (for example add new 'Cancel' status to an Order), will this cause contention with active users?  For example if someone is within an order at the same time I add new value to Picklist - could it effect the user's experience?  I didn't see anything on Salesforce that indicated it will, but thought I would check.  We can schedule these changes in off-hours, but just want to make sure clear on our options.

Thanks for the help.

Dan
I am new to SalesForce.  I have created my first Apex Class.  The goal of the class is to read through all ccrz__E_Order__c that are in 'Order Submitted' status.  For each order, read the items.  If all items are currently in 'Received' status, then update the status on the order to 'Order Received'.  I want this to be a batch process ran daily.

Following questions being a first time creator of an Apex Class.
  • Is the best way to trigger this process to run by going into Apex Class and Schedule Apex?  Or is there a more appropriate way to schedule?
  • When testing, I have used Developers Console and executed the class.  However, I run into limits.  Even when I have scheduled in our test environments, i have hit limits.  There are only a couple hundred orders in this status but it hits Too many SOQL queries system limit exception.  Is there a way to test the entire process w/o exceeding these limits?  Are they the same limits when going to production?  When testing I enter specific orders (you'll see commented out line below in the code), but when i read all orders i am hitting these limits.
  • Is there a system log that i can write totals too?  As you can see on the code below i have written beginning / ending / total information into the debug log.  But when going to production, i will comment these out - is there a traditional way to look at this kind of information in a salesforce log.  
I am including the code i have created and appreciate any feedback since this is my first attempt.

Thanks in advance for any advise.  

Dan

/*
 * @Name: Batch_OrderStatus
 * @Author: Dan Norfleet
 * Updates : 02/12/2018 - Dan Norfleet - Created Apex Class
 */
    global class Batch_OrderStatus Implements Schedulable
    {
      global void execute(SchedulableContext sc)
      {
        execute();
      }

      global void execute() 
      {
          System.debug('Batch_OrderStatus BEGIN');
          //  Create a list of Orders in status of 'Order Submitted'
          List<ccrz__E_Order__c> orders = 
                [SELECT Id, Name, ccrz__orderstatus__c 
                FROM ccrz__E_Order__c 
                WHERE ccrz__orderstatus__c = 'Order Submitted'];
//                WHERE ccrz__orderstatus__c = 'Order Submitted' AND Name = 'O-0000112447'];
          Integer total_orders_cnt = 0;
          Integer update_cnt = 0;
          Integer notupdated_cnt = 0;
          String status_received = 'Received';
          String status_order_received = 'Order Received';
          
          // Loop through the list of orders
          for(ccrz__E_Order__c ord : orders)
          { 
            total_orders_cnt = total_orders_cnt + 1;
            String order_id = ord.Id;
            // For each order, see how many items are in 'Received' status
            integer ItemsReceived = 
                    [SELECT COUNT() 
                    FROM ccrz__E_OrderItem__c 
                    WHERE ccrz__orderitemstatus__c = :status_received
                    AND ccrz__order__c = :order_id];
          
            integer ItemsNOTReceived = 
                    [SELECT COUNT() 
                    FROM ccrz__E_OrderItem__c 
                    WHERE ccrz__orderitemstatus__c != :status_received
                    AND ccrz__order__c = :order_id];
          
            if (ItemsReceived > 0)
                    { 
                if (ItemsNOTReceived == 0)
                        { 
                        ord.ccrz__orderstatus__c = status_order_received;
                        System.debug('    UPDATE Order - ID = ' + order_id + ' / Order Name = ' + ord.Name + ' / Items Received = ' + ItemsReceived + ' / ItemsNOTReceived = ' + ItemsNOTReceived);
                        update_cnt = update_cnt + 1;
                        update orders;
                        }
                    else
                        {
                        notupdated_cnt = notupdated_cnt + 1;
                        }
                    }
                
          }
          System.debug('TOTAL ORDERS IN SUBMITTED STATUS = ' + total_orders_cnt);
          System.debug('TOTAL UPDATED = ' + update_cnt);
          System.debug('TOTAL NOTUPDATED = ' + notupdated_cnt);
          System.debug('Batch_OrderStatus END');
      }
}