Processing Forms: Implementation of Processing Operations

A processing operation is implemented as a method that is invoked from a processing or data entry form. On a processing form, you specify the method that is invoked when a user clicks Process or Process All on the form toolbar. On a data entry form, you define a button that invokes the processing method in a separate thread.

You can define a processing method in either of the following ways:
  • Define a non-static method that uses a single record as the input parameter. This way can be used to process a single record independently from other records of the same class.
  • Define a static method that uses a list of records as the input parameter. This way can be used to process a list of records. In this method, you can reorder the records in the list before processing, as well as check dependencies between records during processing.
    Note: If you are using the static method, you can pass a cancellation token in the action delegate and initiate the cooperative cancellation by using the ThrowIfCancellationRequested() method according to the cooperative cancellation pattern in .NET.

The following sections describe these ways of defining a processing method and the ways to display errors and warnings.

Note: You do not need to create a dedicated processing form for each processing operation; the decision of whether to create a processing form depends on the requirements of the application.

Using a Non-Static Processing Method

In a simple case, to process a single record, you can define a non-static processing method in the data entry graph, as the following code shows.

// The data entry graph
public class SalesOrderEntry : PXGraph<SalesOrderEntry, SalesOrder>
{
    ...
    // A non-static processing method that works with a single record
    public void ApproveOrder(SalesOrder order, bool isMassProcess = false)
    {
         // Process the record here
    }
}

To make the system invoke the method in a separate thread, you can use the PXLongOperation.StartOperation() method. Within the method that you pass to StartOperation(), you have to create a new instance of the graph and invoke the processing method on that instance, as the following code shows.

public PXAction<SalesOrder> Approve;
[PXProcessButton]
[PXUIField(DisplayName = "Approve")]
protected virtual IEnumerable approve(PXAdapter adapter)
{
    Actions.PressSave();
    SalesOrder order = Orders.Current;
    PXLongOperation.StartOperation(this, delegate()
    {
        SalesOrderEntry graph = PXGraph.CreateInstance<SalesOrderEntry>();
        graph.ApproveOrder(order);
    });
    return adapter.Get();
}
Note: Use the PXGraph.CreateInstance<T>() method to instantiate a graph from code. Do not use the new T() graph constructor.

The method passed into PXLongOperation.StartOperation() matches the following delegate type, which uses no input parameters.

delegate void PXToggleAsyncDelegate();
Tip: The anonymous method definition (delegate()) is used to shorten the code in the example.

Using a Static Processing Method

In a general case, to process a list of records that may depend on one another, you have to define the static processing method, as the following code shows. The processing method can have a second parameter of the CancellationToken type; you can later use this parameter to initiate the cooperative cancellation by calling the ThrowIfCancellationRequested method.

// The processing graph
public class ReorderProcess : PXGraph<ReorderProcess>
{
    ...
    // Static processing method that works with a list of records
    public static void Process(List<ProductReorder> products, 
        CancellationToken ct = default)
    {
        foreach (var order in list)
        {
            ct.ThrowIfCancellationRequested();
            // Process the single record
            Process(order);
        }
    }    
}

// The data entry graph
public class SalesOrderEntry : PXGraph<SalesOrderEntry, SalesOrder>
{
    ...
    // Static processing method that works with a list of records
    public static void ReleaseDocs(List<ProductReorder> products)
    {
        // Process the records here
    }
}

You can invoke the static processing method in the data entry graph, within the method passed in the PXLongOperation.StartOperation() method, as shown in the following code example.

public PXAction<SalesOrder> Release;
[PXProcessButton]
[PXUIField(DisplayName = "Release")]
protected virtual IEnumerable release(PXAdapter adapter)
{
    Actions.PressSave();
    SalesOrder order = Orders.Current;
    List<SalesOrder> list = new List<SalesOrder>();
    list.Add(order);
    PXLongOperation.StartOperation(this, delegate()
    {
        SalesOrderEntry.ReleaseDocs(list);
    });
    return list;
}

The PXLongOperation.StartOperation() method creates a separate thread and executes the specified delegate asynchronously in this thread.

Tip: You cannot manage the cooperative cancellation for the PXLongOperation.StartOperation() method, so you do not need to specify the cancellation token.

Displaying Messages and Processing Errors

To display a message on a form from a processing method, use the following static methods of the PXProcessing class:

  • SetInfo(): Displays a green check mark for the processed row in the grid, which denotes the successful processing of the record.
  • SetWarning(): Displays an exclamation mark for the processed row in the grid, which indicates that a warning has occurred during processing.
  • SetError(): Displays a red X for the processed row in the grid, which denotes an error that has occurred during processing.

In each of these methods, you have to specify the initial object index in the list that is passed to the processing method. You can also specify the message text that appears for the row.

Note: The same rules are applicable to the processing of errors within the delegate you pass to PXLongOperation.StartOperation(...).

In the following example, you show the green check mark, which represents success, or the X icon, which represents an error, on a form for each processed data record. You then return the error to the UI if the processing of at least one record fails. (The following code attempts to process all records from the list.)

public static void Process(List<ProductReorder> products, 
    CancellationToken ct = default)
{
    ReceiptEntry graph = PXGraph.CreateInstance<ReceiptEntry>();
    bool erroroccurred = false;
    // Reordered list
    List<ProductReorder> productsToProceed =
    products.OrderBy(item => item.SupplierID).ToList();
    ...
    // Process records
    foreach (ProductReorder rec in productsToProceed)
    {
         try
         {
             // Set the green check mark for the item by the initial index in
             // the products list, not in productsToProceed
             PXProcessing<ProductReorder>.SetInfo(
                 products.IndexOf(rec),
                 String.Format("The receipt {0} has been created",
                               doc.DocNbr));
         }
         catch (Exception e)
         {
             // Set the error flag
             erroroccurred = true;
             // Set the error for the item by the initial index in
             // the products list, not in productsToProceed
             PXProcessing<ProductReorder>.SetError(
                 products.IndexOf(rec), "A receipt cannot be created");
             ...
         }
    }
    // Throw the error if at least one record has not been processed
    if (erroroccurred)
        throw new PXException("At least one product has not been processed.");
}
To display the error as a result of processing all records, you have to throw the error at the end of a processing method.