- Created a BPM on Part.Update to provide an informational message popup when Run Out, Inactive, or On Hold is set to true to inform the user if there are open Sales Order or Purchase Order lines.
- Created a Data Directive to record any change to Run Out, Inactive, or Hold in the Change Log.
- Created a Data Directive in the Change Log where Table = Part and if the Log Text references any of those fields to email the Part Number (Key1), Log Text and '#PartAvail' to their Company SocialCast feed. (Other SocialCast users can email subscribe to the tag '#PartAvail' or the Epicor User feed. Something similar can be done with Epicor Social).
- Created a BAQ Search for the Sales Order Search that would display all the Open Sales Order lines where the Part Status is marked as Inactive, Run Out or On Hold.
- Created a BAQ Search for the Purchase Order Search that would display all the Open Purchase Order lines where the Part Status is marked as Inactive, Run Out or On Hold.
Which this all helped, a few days later I received another call from the Purchasing Buyer that customer service used Time Phase frequently to determine when to set Order Due Dates and would like to see the Part Status in that screen somehow. (Note: I have found that most CSR's do not find the "Capable and Available to Promise" functionality very useful and adoption has been impossible despite multiple tries. Most are more comfortable with the Time Phase UI; it's more intuitive to them).
At first, I thought I would need to do a customization to the screen and add the fields to the UI even though it is not something I prefer as it's one more thing we have to check when we upgrade. However, when I reviewed the Time Phase screen carefully, I discovered that the Exception column value for the first row (Due = Null and Source = On-hand Quantity) never had a value. Perhaps I could use a BPM to determine the Part Status value that mattered and set that value on the fly as the data is retrieved.
I performed a quick test to identify the correct Business Object & Method to put the BPM on and to see if I could set the value to something like "On-Hand"... and it worked! It took a few passes with the trace tool to ID the Business Object & Method, but it ended up being the TimePhas.GoProcessTimePhase BO & Method. Post-Processing would be needed so that it would change the value after the Method retrieved the data from the database.
Next, I had to work out the logic for the switching to get the correct meaningful result in the field. While an inactive part can both be set for Run Out and On Hold, it's On Hold status is inconsequential when it's inactive. The intent is to give the user an idea of the part's availability. If they need more, they can review the part in Part Tracker. I wanted to avoid using a custom code to do this, if for no other reason than to prove it can be done. I ended up with a series of 4 Set by Query Setters. (I started with conditionals and trying to set BPMContext fields, but I quickly discovered that it was quite redundant). I determined that the right order to evaluate the part status to get the correct value would be to evaluate the various status fields in this order: OnHold, RunOut, InActive.
1. Set Active Query for ttTimePhas
1. Set Active Query for ttTimePhas
This query doesn't actual check against the Part table itself, it is just setting the default for the field on the correct row. I considered just leaving the value blank, but then I would have no confirmation that the BPM was firing. (In 10.0 there is a bug where BPM's will stop firing). Additionally, it's clearer to the end user.
This query is just against the ttTimePhas table. I passed the Part Number as an argument just for good form and since then I only need to change the Exception Reason value for the rows where the Source column is 'On-Hand Quantity'. I have set a criteria for that as well; this will be repeated for the ttTimePhas table in each subsequent query.
Then, for all the rows identified by that query I am setting the ttTimePhas.ExceptionReason to 'Active'.the ttTimePhas table. I passed the Part Number as an argument just for good form and since then I only need to change the Exception Reason value for the rows where the Source column is 'On-Hand Quantity'. I have set a criteria for that as well; this will be repeated for the ttTimePhas table in each subsequent query. Then, for all the rows identified by that query I am setting the ttTimePhas.ExceptionReason to 'Active'.
2. Part Hold and OnHoldDate Query Setup
2. Part Hold and OnHoldDate Query Setup
The next query will evaluate the Part's Hold status. What is tricky with this is that along with the Hold, there is an OnHoldDate field which can be set in the future. So, there needs to be an additional criteria on the OnHoldDate field to only select if the date is today or in the past. If it's not on hold yet, it's not on hold and it shouldn't say it is. Consider the following points when crafting queries that involve joins with database tables.
First, be sure to set a good join between the tt 'table' and the database table. There does not seem to be any automatic joins, so understand the data in the tt (temporary table) and create the appropriate joins. While doing this, I noticed that ttTimePhas does not have a Company field. I wanted to be sure I was only selecting the Part records for the current company, so I added a criteria to the Part table where Company = BAQ CurrentCompany constantly. It doesn't hurt to be sure.
Do not forget to apply the same criteria on the ttTimePhas table as in the preceding widget. The widget should exclusively select and update the 'On-Hand Quantity' row. Then, on the Set by Query widget the ExceptionReason field will be set to 'OnHold!', overriding the previous Set by Query widget if the part is on hold.
3. Configuring Set by Query Widgets for RunOut or InActive Cases
The next two Set by Query widgets will all be similar to the On Hold query except the RunOut or InActive field will be evaluated and there is no need to check the OnHoldDate.
Again, don't forget the ttTimePhase criteria, the joins and a criteria on Part for Current Company. Then, just set appropriate messages for the ExceptionReason.
4. Final Configuration and Testing
I learned a few new things with this solution:
- Manipulation of most field values in the UI can be done with the appropriate Business Object Method in Post processing no matter if it's a read only control or not, nor if it's an Entry form or a Tracker form.
- If values manipulated via other methods are set before an Update they are usually then available in the update, which can provide better feedback to the user when setting defaults, that way fields aren't 'magically' changing when the Update method is called.
- Explore the Business Object methods, there are so many of them and while often times we end up landing on the Update method, some other method may be more appropriate to use, and in some cases easier. Using the trace feature is very valuable in understanding the role of each method and when it is called.