Pages

Monday, November 18, 2013

AX Retail 2012 R2: Customization of Real-time Service

This blog, I will show you some examples how to customize things relating to Real-time Service.  


Scenario  

During -1/+1 hour of store closing time. Before "Tender declaration" or "Close shift", check whether or not there is opened customer order which is requested to pick up at that store on current date. 







This relates to Real-time Service because POS needs to check opened customer order from Retail HQ. 

Store shipping warehouse and closing hour

Shipping warehouse and closing hour are configured in Retail stores detail form; Retail > Common > Retail channels > Retail stores   




Retail HQ: Method for checking opened customer order

We needs to create a method to return a set of orders with status is opened, delivery date is current date and delivery warehouse is same as the store. 

Add the following method in the RetailTransactionServiceEX class.  The method will return a set of sales orders in container array. 


public static container pkaSearchSalesOrderOpenOrderList(str _storeId)
{
    SalesTable                  salesTable;

    container                   salesOrder = [true,''];

    RetailStoreId               retailStoreId = _storeId;
    RetailStoreTable            retailStoreTable = RetailStoreTable::find(retailStoreId);
    SalesShippingDateRequested  requestedDeliveryDate = systemDateGet();

    if(retailStoreTable.RecId)
    {
        while select SalesId
        from salesTable
        order by salesTable.CreatedDateTime desc
        where (
                //Result must not contain SalesStatus::Canceled && SalesStatus::Delivered && SalesStatus::Invoiced
                (salesTable.SalesStatus == SalesStatus::None ||
                 salesTable.SalesStatus == SalesStatus::Backorder)
                //Check delivery date as current date
                && (salesTable.DeliveryDate == requestedDeliveryDate)
                //Check warehouse same as retail store
                && (salesTable.InventLocationId == retailStoreTable.inventLocation)
              )
        {
            salesOrder = conins(salesOrder, conlen(salesOrder) + 1, RetailTransactionService::getSalesOrder(salesTable.SalesId));
        }
    }

    return salesOrder;
}

Retail POS: Calling extension methods 

Create a Blank Operation button with: -

  • operation number: CHECKSALESORDER 
  • param: TENDERDECLARATION or CLOSESHIFT --> this will be process after checking order


I will simply create functions in BlankOperations.cs of Retail SDK for processing above operation number and its param. 

        /// <summary>
        /// Displays an alert message according operation id passed.
        /// </summary>
        /// <param name="operationInfo"></param>
        /// <param name="posTransaction"></param>        
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Grandfather")]
        public void BlankOperation(IBlankOperationInfo operationInfo, IPosTransaction posTransaction)
        {

            switch ((operationInfo.OperationId).ToUpperInvariant().Replace(" ", string.Empty))
            {
                #region Check Sales Order
                case "CHECKSALESORDER": //Check Sales Order
                    if (this.CheckNullOperationParam(operationInfo))
                    {
                        this.CheckSalesOrder(operationInfo); 
                    }
                    break;
                #endregion
                default:
                    //default, just echo the operation number and parameter value 
                    break;
            }
        }

For this Blank Operation button, parameter is mandatory.  Make sure that param is defined.  

        private bool CheckNullOperationParam(IBlankOperationInfo operationInfo)
        {
            StringBuilder comment = new StringBuilder(128);
            bool ret = true; 

            if (operationInfo.Parameter == "")
            {
                //throw error if operation parameter is empty 
                comment.Append("Operation parameter not found.");
                SerializationHelper.ShowMessage(comment.ToString()); 
                // Set this property to true when your operation is handled
                operationInfo.OperationHandled = true;
                ret = false;
            }
            return ret; 
        }

As the scenario "During -1/+1 hour of store closing time", we have to compare current time with closing hour configuration from Store table.  

Getting StoreId from LSRetailPosis.Settings.ApplicationSettings.Terminal.StoreId and get closing time from RetailStoreTable in Store DB. 

If no opened order, user is able to "Tender declaration" or "Close shift" by calling Application.RunOperation command.   

        private void CheckSalesOrder(IBlankOperationInfo operationInfo)
        {
            string storeId = LSRetailPosis.Settings.ApplicationSettings.Terminal.StoreId;
            bool isStoreClosingHour = DataCollection.IsStoreClosingHour(Application, storeId);
            bool chkSalesOrder = false;

            if (isStoreClosingHour)
            {
                chkSalesOrder = this.CheckSalesOrderExists(operationInfo); 
            }
            else
            {
                chkSalesOrder = true; 
            }

            if (chkSalesOrder)
            {
                switch ((operationInfo.Parameter).ToUpperInvariant().Replace(" ", string.Empty))
                {
                    case "TENDERDECLARATION":  //Tender Declaration
                        Application.RunOperation(PosisOperations.TenderDeclaration, null);
                        break;
                    case "CLOSESHIFT":  //Tender Declaration
                        Application.RunOperation(PosisOperations.CloseShift, null);
                        break;
                    default:
                        StringBuilder comment = new StringBuilder(128);
                        comment.AppendFormat("Specified action for operation {0} not found", operationInfo.Parameter);
                        SerializationHelper.ShowMessage(comment.ToString());
                        break;
                }
            }
        }

        /// <summary>
        /// Check whether or not system will check opened Sales Order at the moment
        /// </summary>
        /// <param name="Application"></param>
        /// <param name="StoreId"></param>
        /// <returns>Return True status for checking opened Sales Order at the moment</returns>
        internal static bool IsStoreClosingHour(IApplication Application, string StoreId)
        {
            bool ret = false; 
            DataTable store = new DataTable();
            string queryString = "SELECT DATEDIFF(HOUR, CONVERT(TIME, SYSDATETIME()), CONVERT(TIME, DATEADD(SECOND, [OPENTO], 0), 114)) AS TIMEDIFF " + 
                                    "FROM RETAILSTORETABLE AS STORE " +
                                    "WHERE STORE.STORENUMBER = '" + StoreId + "' ";
            store = DataCollection.GetData(Application, queryString);
            if (store.Rows.Count > 0)
            {
                DataRow row = store.Rows[0];
                // if current time is different form 1 / -1 hour of "Opening To" hour of store
                // Return True status for checking opened Sales Order at the moment

                if ((int)row["TIMEDIFF"] >= -1 && (int)row["TIMEDIFF"] <= 1)
                {
                    ret = true; 
                }
            }


            return ret;
        }

Consuming the Real-time Service extension methods from a POS, we need to call InvokeExtensionMethod with the method name and parameter StoreId. 

        private bool CheckSalesOrderExists(IBlankOperationInfo operationInfo)
        {
            StringBuilder comment = new StringBuilder(128);
            bool ret = true;

            // Begin by checking if there is a connection to the Transaction Service
            this.Application.TransactionServices.CheckConnection();

            string storeId = LSRetailPosis.Settings.ApplicationSettings.Terminal.StoreId;

            ReadOnlyCollection<object> containerArray;
            DataTable salesOrders = new DataTable(); 
            bool retValue;
            string retComment;

            containerArray = this.Application.TransactionServices.InvokeExtension("pkaSearchSalesOrderOpenOrderList", storeId);
            retValue = SerializationHelper.ConvertToBooleanAtIndex(containerArray, 1);
            retComment = containerArray[2].ToString();

            salesOrders.Columns.Add("SALESID", typeof(string));

            for (int i = 3; i < containerArray.Count; i++)
            {

                IList salesRecord = (IList)containerArray[i];

                bool recordRetVal = SerializationHelper.ConvertToBooleanAtIndex(salesRecord, 0);
                string recordComment = SerializationHelper.ConvertToStringAtIndex(salesRecord, 1);

                // The particular sales order at this position in the container is blank so we need
                // to jump over it process the next one...
                if (!recordRetVal)
                    continue;

                DataRow row = salesOrders.NewRow();

                // some of these fields may not be properly initialized even if recordRetVal is true
                row["SALESID"] = SerializationHelper.ConvertToStringAtIndex(salesRecord, 2);

                salesOrders.Rows.Add(row);
            }

            if (salesOrders.Rows.Count > 0)
            {
                if (salesOrders.Rows.Count == 1)
                {
                    DataRow row = salesOrders.Rows[0];
                    comment.AppendFormat("Outstanding sales order {0} is found.", row["SALESID"]);
                }
                else
                {
                    comment.AppendFormat("There are outstanding sales orders found.");
                }
                SerializationHelper.ShowMessage(comment.ToString()); 
                
                // Set this property to true when your operation is handled
                operationInfo.OperationHandled = true;
                ret = false;
            }

            return ret; 
        }

Tip:  I also create another utility class for converting values and showing message by calling frmMessage dialog form.

        internal static bool ConvertToBooleanAtIndex(IList list, int index)
        {
            try
            {
                return Convert.ToBoolean(list[index]);
            }
            catch
            {
                return false;
            }
        }

        internal static string ConvertToStringAtIndex(IList list, int index)
        {
            try
            {
                return Convert.ToString(list[index]);
            }
            catch
            {
                return string.Empty;
            }
        }

        internal static void ShowMessage(string message, MessageBoxIcon icon = MessageBoxIcon.Error)
        {
            using (LSRetailPosis.POSProcesses.frmMessage dialog = new LSRetailPosis.POSProcesses.frmMessage(message, MessageBoxButtons.OK, icon))
            {
                LSRetailPosis.POSProcesses.POSFormsManager.ShowPOSForm(dialog);
            }
        }



No comments:

Post a Comment