Cancelling a Sales Order programmatically in Dynamics AX 2009

There are many scenarios you have to consider while cancelling a sales order programmatically.  There may be some lines picked, some lines delivered and some as open, all on the same order, so unfortunately it’s not as simple as changing the status on the line.  A client of ours asked for the functionality which would automatically cancel a sales order, all the way up to the status of delivered.  This blog is going to demonstrate how to do this:


Line Status

Below are three lines at different stages of a sales order.


The first has been delivered, the second has been picked and the third is an open order line.

Identifying the Status of a Line

The first line is identified as delivered by using one of the following two methods:

//Method 1
if(salesLine.remainSalesFinancial != 0)
    // I am delivered
//Method 2
if(invnetTrans.statusIssue == StatusIssue::Deducted)
    // I am delivered

Note: salesLine.salesStatus == SalesStatus::Deducted cannot be used, as there could be 2 quantity on the line.  2 items could be delivered, but only one invoiced, so the line status would remain as Delivered.

The second line is identified as picked by checking the status on the InventTrans table:

if(inventTrans.statusIssue == StatusIssue::Picked)
    // I am picked

The final line is identified as an open order, which could be either physically reserved, ordered reserved or on order:

if(inventTransBackOrder.statusIssue == StatusIssue::OnOrder ||
   inventTransBackOrder.statusIssue == StatusIssue::ReservOrdered ||
   inventTransBackOrder.statusIssue == StatusIssue::ReservPhysical)
    // I am an open line


Cancel a Sales Line with Status of Delivered

Once you have established the status of the order line, you then have to do something with it.

The following code cancels the sales order line which has a status of delivered.

//update Sales Line
salesLine = SalesLine::find(_salesID, _lineNum, true);
salesLine.SalesDeliverNow   = -1;
//Post the delivery note
salesFormLetter = SalesFormLetter::construct(DocumentStatus::PackingSlip);
salesFormLetter.progressHide();                                // Hide the progress bar.
salesFormLetter.update(salesTable,                             // SalesTable
                       SystemDateGet(),                        // Delivery date
                       SalesUpdate::DeliverNow,                // Quantity to update (SpecQty)
                       AccountOrder::None,                     // AccountOrder
                       false,                                  // Proforma only?
                       false);                                 // Printout?

Cancel a sales Line with Status of Picked

If the line has been picked, it needs to be unpicked and cancelled:

// Initialise classes
salesLine       = salesLine::find(_salesID, _lineNum, true);
inventMovement  = InventMovement::construct(salesLine);
inventTranspick = new InventTransWMS_Pick(inventMovement,tmpInventTransWMS);
// Create transaction record
TmpInventTransWMS.LineNum     = _LineNum;
TmpInventTransWMS.InventDimId = InventTrans::findTransId(salesLine.InventTransId).InventDimId;
TmpInventTransWMS.InventQty   = ( ABS( InventTrans::findTransId(salesLine.InventTransId).Qty ) * -1 );
if( TmpInventTransWMS.InventQty != 0 )
    if( inventTranspick.validateTmp(TmpInventTransWMS) )
        // Post the pick update
//cancel order
salesLine.RemainSalesPhysical = 0;
salesLine.remainInventPhysical = 0;


Cancel an Open Sales Order Line

An open sales line could have a status of “on order”, “physically reserved” or “ordered reserved”.
The following code will cancel a line with one of these statuses:

//cancel order
salesLine.RemainSalesPhysical = 0;
salesLine.remainInventPhysical = 0;


Changing the status of the Sales Order Header to Cancelled

The sales status on the sales order header will automatically change to “Cancelled” when all lines have been cancelled on the order.


Cancelling a Sales Order with an Invoiced Sales Line

Once a sales order line has been invoiced, the line cannot be cancelled.

If there is a sales order with multiple lines, one of which is invoiced, the lines with a status other than invoiced will be “cancelled” and the sales order header status shall update to “Invoiced”.


Cancelling Return Orders

If a return order needs to be cancelled, there is standard code which already does this (SalesTable::changeReturnStatus(FormDataSource _datasource, ReturnUpdateAction _updateAction).

I have used this code and created my own method by tweaking it slightly to allow a table to be passed in as below:


public void K3CancelReturnOrder(SalesTable _returnTable)
    SalesLine       returnLine;
    SalesTable      returnTableUpdate;
    Counter         numberOfRecords;
    SalesTable      salesTable;
    PurchTable      purchTable;
    PurchLine       purchLine;
        if (_returnTable.ReturnStatus == ReturnStatusHeader::Created && !_returnTable.existRegisteredReceivedInvoicedLines() && !_returnTable.type().interCompanyIsDerivedOrder())
            while select forupdate returnLine
                  where returnLine.SalesId == _returnTable.SalesId
                  &&    returnLine.InventTransId
                if (returnLine.interCompanySalesLineExist())
                    purchLine            = PurchLine::findInventTransId(returnLine.InventRefTransId,true);
                    purchTable           = PurchTable::find(purchLine.PurchId);
                    purchLine.SkipUpdate = InterCompanySkipUpdate::Internal;
                    if (!purchTable.existPurchLine())
                        if (purchTable.InterCompanyCompanyId
                        &&  purchTable.InterCompanySalesId)
                                salesTable = null;
                                salesTable = SalesTable::find(purchTable.InterCompanySalesId,true);
                                if (salesTable)
                returnLine.SalesQty     = 0;
                returnLine.DeliveryType = TradeLineDlvType::None;
                returnLine.SkipUpdate   = InterCompanySkipUpdate::Internal;
            update_recordset returnLine
            setting     ReturnStatus         = ReturnStatusLine::Canceled,
                        CostPrice            = 0,
                        InventTransIdReturn  = '',
                        SalesStatus          = SalesStatus::Canceled,
                        ReturnClosedDate     = systemdateget()
            where returnLine.SalesId == _returnTable.SalesId;
            returnTableUpdate = SalesTable::find(_returnTable.SalesId, true);
            returnTableUpdate.ReturnStatus               = ReturnStatusHeader::Canceled;
            returnTableUpdate.SalesStatus                = SalesStatus::Canceled;
            returnTableUpdate.InterCompanyDirectDelivery = false;

Pick List Registration

Note: the implementation I have been working does not use pick list registration so I looked at whether it will work under these circumstances.



Hopefully, if it is your first time working with cancelling sales order lines, this post should save you a bit of time and give you a better understanding of the process.