Forcing Cancellation Code when deleting Sales Orders into history through BOI

SOLVED

Good morning all,

So I figured this would have been a relatively easy script but I can't seem to find a way of accomplishing what I need.

I have a customer that would like to force a cancellation reason whenever you delete a Sales Order into history.  They currently have the "Retain Deleted Sales Order" setting to Prompt in SO Options, which first prompts the user if they would like to delete the order (standard), but THEN it asks "Do you want to save the deleted order in history?"  If you say yes, it's all good.  However, you can say No and it will delete the order and not save it (a BIG no no for the customer).  Furthermore, if you do click Yes to save to history, then it bring up this screen to enter the Cancellation Code:

Cancellation Code reason

While I have the Cancellation Code as a required field in Advanced Settings for UDF and Table Maintenance, it will not force an entry unless I validate the field with a list I create, which I would have to make it match the existing lookup on this field.  If I don't have validation, the user can simply click OK without a code and it will delete it.

So I was thinking my quick fix would be to set "Retain Deleted Sales Order" setting to Yes, then create a Table - Pre Delete script that would launch the Cancellation Code screen above BEFORE it asks to delete the order.  I figured that by doing this, the Cancellation Code would be written into the table and then the order would be deleted into history.
Unfortunately, I cannot seem to find the correct UI to bring that screen.  This is the code I had tried but it brings up Cancel and Reason Code Maintenance On The Fly.   

oSOCancel = 0

oSOCancel = oSession.GetObject("SO_CancelReasonCodeOTF_UI")
Set oSOCancel = oSession.AsObject(oSOCancel)
retVal = oSOCancel.Process()

I tried the SO_CancelReasonCode_UI object as well, but that brings up the standard Cancel and Reason Code Maintenance.  
Is there a way of bringing up that specific screen?  I had also tried using the InvokeButton to bring up "fldr.DCANCEL" (Cancellation Code screen in SO_SalesOrder.M4L library) but I had no luck.  Maybe I was using the wrong code?  Any ideas or suggestions would be highly appreciated.

Thanks in advance!
Javier

Parents
  • 0

    Instead of trying to launch the panel, you might be better off invoking the SO_CancelReason lookup instead. I'm not sure if this will actually work in a UDS as long as you have a handle to the UI object, such as oSession.AsObject(oScript.UIObj).InvokeLookup but if it does, you will want to make sure you are actually filtering for very specific details, such as the UI being present in the first place, can use oScript.UIObj <> 0, the start program, might want to make sure it is SO_SalesOrder_UI, make sure oSession.Updating = 0, etc.

    Another way to do this might be to use oSession.AsObject(oScript.UIObj).NomadsProcess "DCANCEL", "SO_SalesOrder.M4L" but you may need either a UI post exit script or a fake OK button to run a button script to capture the value entered in the field so you can set it into a storage variable using oSession.AsObject(oSession.ScriptObject).SetStorageVar. You would then use oSession.AsObject(oSession.ScriptObject).GetStorageVar in your original script to get the value that was entered. You could then loop if the value returned is blank in case the user just tried to click OK or Cancel. 

    I would try the InvokeLookup first though as it will be much cleaner and efficient. The first argument is the lookup name, i believe "SO_CancelReason" should give you what you want. The second argument is the variable you want the select value returned in. Make sure you initialize the variable as a blank string before you pass it to the method.

  • 0 in reply to David Speck

    Awesome, thanks for the great info as always .  I'm going to take it and run with it.  If I stumble (I'm sure I will), I'll post a reply.  

  • 0 in reply to Javier Guzman

    My concern would be that anything set in a script during the Pre-Delete event might not make it into SO History.

  • 0 in reply to Kevin M

    Good point, may actually have to move to a post delete and attempt to update the history record afterwards. I haven't messed with history and pre deletes much.

  • 0 in reply to David Speck

    I'd almost lean towards a custom button, invoking BT_ACCEPT first (prompting then setting the reason code value), then re-opening the SO to process the delete.

  • 0 in reply to Kevin M

    That's what I'm seeing.  I can bring up the lookup, select it, and it looks like it deletes ok.  However, it does not flow into SO_SalesOrderHistory as a pre-delete script.  I will try David's suggestion as a post-delete and write to history.

  • 0 in reply to Javier Guzman

    You could use a fake Delete button, hide the real one, have the fake delete button prompt with the same verbiage confirming they want to delete the order, if they answer No, do nothing, if they answer Yes, then proceed to invoke the lookup, get the value back, set it into the cancel reason field, either use oBusObj.Write or oUIObj.BT_Accept/oScript.InvokeButton "BT_Accept", then then call oBusObj.Delete and pass it the sales order number, may then need to follow up with oUIObj.BT_Cancel/oScript.InvokeButton "BT_Cancel".

    If you use the InvokeButton methods, you may also need the oUIObj.HandleScriptUI immediately after each InvokeButton. You can check the return value when doing the "accept" step to see if the order was saved successfully or if an error or warning occurred. The only reason the final step for BT_Cancel might be needed is if the panel doesn't reset to the default state where the fields are empty after you use the finish the "accept" step. If you use oUIObj.BT_Cancel, you may need to follow up after it immediately with oUIObj.RefreshScrn

  • 0 in reply to David Speck

    Hi David,
    Thank you again for your excellent advice.  I am currently working on the fake button method (slowly but surely, one step at a time since I'm still a newbie at this) and I was able to create a fake button, which once pressed gives me the two options.  When clicking Yes, I invoke the lookup and catch the value, which I then try to pass onto the order.  However, it is not writing it back to the order for some reason.  Is it because by invoking the lookup it loses the handle to the SO_SalesOrder_bus?  I thought so, so I made sure to call it back.  However, it still doesn't work.   Here is my code (a work in progress)

    r=oScript.DebugPrint("Deleting Sales Order with Custom Delete button")
    sMsg = "StartProgram = " & oSession.StartProgram & " Updating = " & CStr(oSession.Updating)
    r=oScript.DebugPrint(sMsg)
    oUIObj = 0
    response = ""
    oSOWrite = ""
    oSOCancel = ""

    If IsObject(oUIObj) = False Then
    oUIObj = oScript.UIObj
    Set oUIObj = oSession.AsObject(oUIObj)
    End if

    If oScript.UIObj <> 0 and oSession.Updating = 0 then

    SONo = ""
    retVal = oBusObj.GetValue("SalesOrderNo$",SONo)
    r=oScript.DebugPrint("SONo = " & SONo)
    sMsg = ("Do you want to delete Sales Order " & SONo)
    r=oScript.DebugPrint(sMsg)
    response = oSession.AsObject(oSession.UI).MessageBox("",sMsg,"style=YesNo,icon=?")

    If response = "YES" Then
    sMsg = ("Deleting SO " & SONo)
    r=oScript.DebugPrint(sMsg)

    retVal = oUIObj.InvokeLookup("SO_CancelReason",oSOCancel)
    sMsg = ("Cancel Code = " & oSOCancel)
    r=oScript.DebugPrint(sMsg)
    set oSOWrite = oSession.AsObject(oSession.GetObject("SO_SalesOrder_bus"))
    retVal = oSOWrite.SetKeyValue("SalesOrderNo$",SONo)
    retVal = oSOWrite.SetKey()
    retVal = oSOWrite.SetValue("CancelReasonCode$",oSOCancel)
    retVal = oSOWrite.Write()
    retVal = oSOWrite.Clear()

    oUIObj.BT_Accept

    Else
    sMsg = ("Not deleting SO " & SONo)
    r=oScript.DebugPrint("sMsg = " & sMsg)
    End If

    End If

  • 0 in reply to Javier Guzman

    I would do something more along the lines of this. 

    sMsg = "Deleting Sales Order with Custom Delete button"
    oScript.DebugPrint sMsg
    sMsg = "StartProgram = " & oSession.StartProgram & " Updating = " & CStr(oSession.Updating)
    oScript.DebugPrint sMsg
    
    If oScript.UIObj > 0 And oSession.Updating = 0 Then
    	If Not(IsObject(oUIObj)) Then Set oUIObj = oSession.AsObject(oScript.UIObj)
    	sSalesOrderNo = "" : oBusObj.GetValue "SalesOrderNo$", sSalesOrderNo
    	oScript.DebugPrint "sSalesOrderNo: " & sSalesOrderNo
    	sMsg = "Do you want to delete Sales Order " & sSalesOrderNo
    	oScript.DebugPrint sMsg
    	sResponse = "" : sResponse = oSession.AsObject(oSession.UI).MessageBox("", sMsg, "style=YesNo,icon=?")
    	If UCase(sResponse) = UCase("Yes") Then
    		sMsg = "Deleting SO " & sSalesOrderNo
    		oScript.DebugPrint sMsg
    		sCancelReasonCode = "" : oUIObj.InvokeLookup "SO_CancelReason", sCancelReasonCode
    		oBusObj.SetValue "CancelReasonCode$", sCancelReasonCode
    		If oUIObj.BT_Accept() <> 0 Then
    			oBusObj.Delete sSalesOrderNo
    			oUIObj.BT_Cancel
    			oUIObj.RefreshScrn
    		End If
    	Else
    		sMsg = "Not deleting SO " & sSalesOrderNo
    		oScript.DebugPrint sMsg
    	End If
    End If

    A button script can already access the oBusObj so there is no need to get another handle as you are currently doing, more than likely, your current SetKey is failing because the sales order is still in use by the current oBusObj. 

    I also initialize my variables on the same line they are used to be sure that they are initialized and not forgotten, which can happen if you always put them at the beginning. 

    VBScript has a weird nuance when using parenthesis with a method when you aren't returning the result into a variable, think this happens either when the method has only one argument or more than one argument, been a while since i've seen it because i stopped using the parenthesis when they weren't needed. I only use them if i'm returning the value into a variable or if i need to evaluate the returned value.

Reply
  • 0 in reply to Javier Guzman

    I would do something more along the lines of this. 

    sMsg = "Deleting Sales Order with Custom Delete button"
    oScript.DebugPrint sMsg
    sMsg = "StartProgram = " & oSession.StartProgram & " Updating = " & CStr(oSession.Updating)
    oScript.DebugPrint sMsg
    
    If oScript.UIObj > 0 And oSession.Updating = 0 Then
    	If Not(IsObject(oUIObj)) Then Set oUIObj = oSession.AsObject(oScript.UIObj)
    	sSalesOrderNo = "" : oBusObj.GetValue "SalesOrderNo$", sSalesOrderNo
    	oScript.DebugPrint "sSalesOrderNo: " & sSalesOrderNo
    	sMsg = "Do you want to delete Sales Order " & sSalesOrderNo
    	oScript.DebugPrint sMsg
    	sResponse = "" : sResponse = oSession.AsObject(oSession.UI).MessageBox("", sMsg, "style=YesNo,icon=?")
    	If UCase(sResponse) = UCase("Yes") Then
    		sMsg = "Deleting SO " & sSalesOrderNo
    		oScript.DebugPrint sMsg
    		sCancelReasonCode = "" : oUIObj.InvokeLookup "SO_CancelReason", sCancelReasonCode
    		oBusObj.SetValue "CancelReasonCode$", sCancelReasonCode
    		If oUIObj.BT_Accept() <> 0 Then
    			oBusObj.Delete sSalesOrderNo
    			oUIObj.BT_Cancel
    			oUIObj.RefreshScrn
    		End If
    	Else
    		sMsg = "Not deleting SO " & sSalesOrderNo
    		oScript.DebugPrint sMsg
    	End If
    End If

    A button script can already access the oBusObj so there is no need to get another handle as you are currently doing, more than likely, your current SetKey is failing because the sales order is still in use by the current oBusObj. 

    I also initialize my variables on the same line they are used to be sure that they are initialized and not forgotten, which can happen if you always put them at the beginning. 

    VBScript has a weird nuance when using parenthesis with a method when you aren't returning the result into a variable, think this happens either when the method has only one argument or more than one argument, been a while since i've seen it because i stopped using the parenthesis when they weren't needed. I only use them if i'm returning the value into a variable or if i need to evaluate the returned value.

Children
  • 0 in reply to David Speck

    Thanks so much for this, David.  Lots of good stuff here for me to learn.

    I tried the script and unfortunately it still does not write to the SO Header, which then does not write to history.  Also, I noticed it creates a blank records in SO_SalesOrderHistoryHeader table.  I tried adding oBusObj.Write() after the SetValue but that didn't do anything either :(  

  • 0 in reply to Javier Guzman

    May need to send oBusObj.LastErrorMsg to the trace window after using SetValue on the CancelReasonCode field, i noticed when using Sales Order Entry, i could not manually set a cancel reason unless i changed the order status to Hold first. That might be a requirement here as well. I haven't had a chance to actually test the script, merely rewrote yours. I may get a chance to do some testing later today.

  • 0 in reply to David Speck

    You are BRILLIANT!  That is exactly why it wasn't working.  Debug print on oBusObj.LastErrorMsg was "You cannot set if Order status is not Hold"

  • 0 in reply to Javier Guzman
    SUGGESTED

    As for the blank records in history, that may or may not have been caused by failed iterations of your script attempts. 

    For the Hold issue, just add another SetValue for the status field and pass the "H" as the value before you set the cancel reason code.

  • 0 in reply to David Speck

    Yes, that worked perfectly.  Set the order to hold before changing the reason code and we are in business!  Well, almost...
    While the order does in fact delete and go to Sales Order History, I noticed the OrderStatus is A (Active).  The blank record continues to be created BUT it has an OrderType of X (Deleted)
    I did a LastErrorMsg right after the Delete sSalesOrderNo and it says "The   is invalid" which maybe it's trying to delete a " " record and thus saying its invalid?  

  • 0 in reply to Javier Guzman
    SUGGESTED

    I've seen "The   is invalid" a lot as false positives, not saying that isn't the case here, but it could be.

    Might need to try an alternate method to get the record back into an edit state, could try oUIObj.InvokeChange "SalesOrderNo", sSalesOrderNo before the oBusObj.Delete and not pass anything to oBusObj.Delete.

  • 0 in reply to David Speck

    Well, I worked on this off and on this weekend and was able to get it to delete the record without creating the blank record in history.  However, when I do that, it does not keep the ReasonCancelCode.  I then tried to see how standard Sage 100 works with holds.  I entered a new order and put it on hold so I could place a reason code.  Then, I went back to the order and deleted it using the standard button.  When I went to history, the record is deleted as expected but the ReasonCancelCode is cleared.  The ONLY way you can set it (or so it seems) is by using the Prompt setting when deleting.  Maybe the SO_CancelReason lookup is only for Hold?  I noticed the window that does pop up when deleting an order (using the Prompt setting) is different (DCANCEL screen).  

  • +1 in reply to David Speck
    verified answer

    I think it might be problematic to write back into that CancelReasonCode field unless I write once it is in history.  In the meantime, I will create a UDF called CANCELREASON and write the value into it.  At some point, I may pursue writing that value into the CancelReasonCode from the UDF.

    Here is the final code.  I am forcing the user to select a reason, otherwise they will get a popup and the order won't be deleted.  

    Thank you and for your knowledge and excellent assistance.  It is very appreciated.

    sMsg = "Deleting Sales Order with Custom Delete button"
    
    oScript.DebugPrint sMsg
    sMsg = "StartProgram = " & oSession.StartProgram & " Updating = " & CStr(oSession.Updating)
    oScript.DebugPrint sMsg
    
    If oScript.UIObj > 0 And oSession.Updating = 0 Then
    
    	If Not(IsObject(oUIObj)) Then Set oUIObj = oSession.AsObject(oScript.UIObj)
    	sSalesOrderNo = "" : oBusObj.GetValue "SalesOrderNo$", sSalesOrderNo
    	oScript.DebugPrint "sSalesOrderNo: " & sSalesOrderNo
    	sMsg = "Do you want to delete Sales Order " & sSalesOrderNo
    	oScript.DebugPrint sMsg
    	sResponse = "" : sResponse = oSession.AsObject(oSession.UI).MessageBox("", sMsg, "style=YesNo,icon=?")
    
    	If UCase(sResponse) = UCase("Yes") Then
    		sMsg = "Deleting SO " & sSalesOrderNo
    		oScript.DebugPrint sMsg
    		sCancelReasonCode = "" : oUIObj.InvokeLookup "SO_CancelReason", sCancelReasonCode
    		oBusObj.SetValue "UDF_CANCELREASON$", sCancelReasonCode
    		oBusObj.Write()
    		oScript.DebugPrint oBusObj.LastErrorMsg
    
    		If oUIObj.BT_Accept() <> 0 and sCancelReasonCode <> "" Then
    			oBusObj.Delete sSalesOrderNo
    			oScript.DebugPrint oBusObj.LastErrorMsg
    			oUIObj.BT_Cancel
    			oUIObj.RefreshScrn
    			Else sMsg = "You must select Cancel Reason Code to delete this order"
    			oScript.DebugPrint sMsg
    			sResponse = "" : sResponse = oSession.AsObject(oSession.UI).MessageBox("", sMsg, "style=OK,icon=?")
    			
    		End If		
    	Else
    		sMsg = "Not deleting SO " & sSalesOrderNo
    		oScript.DebugPrint sMsg
    	End If
    
    End If

  • 0 in reply to Javier Guzman

    Had some time to dig a little deeper into this and there is a property of the SO_SalesOrder_Bus object called SaveDeletedReason. Setting this before deleting the order will set the cancel reason code in history and there is no need to "write" the change to the current order which means you should be able to accomplish this in a pre-delete script. I was testing with a button script for ease of troubleshooting it.

    I've revised the script posted earlier to use this.

    sMsg = "Deleting Sales Order with Custom Delete button"
    oScript.DebugPrint sMsg
    sMsg = "StartProgram = " & oSession.StartProgram & " Updating = " & CStr(oSession.Updating)
    oScript.DebugPrint sMsg
    
    If oScript.UIObj > 0 And oSession.Updating = 0 Then
    	If Not(IsObject(oUIObj)) Then Set oUIObj = oSession.AsObject(oScript.UIObj)
    	sSalesOrderNo = "" : oBusObj.GetValue "SalesOrderNo$", sSalesOrderNo
    	oScript.DebugPrint "sSalesOrderNo: " & sSalesOrderNo
    	sMsg = "Do you want to delete Sales Order " & sSalesOrderNo
    	oScript.DebugPrint sMsg
    	sResponse = "" : sResponse = oSession.AsObject(oSession.UI).MessageBox("", sMsg, "style=YesNo,icon=?")
    	If UCase(sResponse) = UCase("Yes") Then
    		sMsg = "Deleting SO " & sSalesOrderNo
    		oScript.DebugPrint sMsg
    		sCancelReasonCode = "" : oUIObj.InvokeLookup "SO_CancelReason", sCancelReasonCode
    		oBusObj.SaveDeletedReason = sCancelReasonCode
    		oBusObj.Delete sSalesOrderNo
    		oUIObj.BT_Cancel
    		oUIObj.RefreshScrn
    	Else
    		sMsg = "Not deleting SO " & sSalesOrderNo
    		oScript.DebugPrint sMsg
    	End If
    End If

    Also, the reason you were getting blank records in history is because you are deleting new records that haven't been saved to the data files yet. In other words, the oBusObj.EditState property is 2. If you want brand new orders saved to history as well, you will need to write the record after first checking that the EditState is 2, if it is a 1, then it is an existing record.

    Since this will be done from Sales Order Entry, i would recommend using the oUIObj.BT_Accept() route so you can check if the record fails any validation before you attempt to delete it.

    If you don't care about brand new orders, you can just Clear the record, you could use oBusObj.Clear but i would recommend oUIObj.BT_Cancel to keep the business and UI object in sync.

    It appears as though the business object clears SaveDeletedReason after the Delete method but it probably wouldn't hurt to reset it yourself to a blank string.

  • 0 in reply to David Speck

    This is awesome!  Thank you so much for doing all this work, David!  I hope it can help others in the future as much as it has helped me.