Understanding BPM's

My company is looking to have the LockQty bool within the PORel table to be toggled with the POHed.Approve bool.

I’m new to BPM’s but have programmed for years previously. I’m for some reason having trouble wrapping my head around the BPM concept. The code below works when there is only a single Release Line within a PO Header but once there is many, it stops working.

What did I do wrong? I’m setting the PO Header variable (assuming that it is grabbing the changed row) without filtering the results. Once it’s found, it validates that null wan’t returned and then sets the PO Release. If that is not null, then it refers back to determine if it needs to set PORel.LockQty = true or false. I’m not a big fan of all the if statements but I can refactor once it’s working properly.

var POHed_Row = ds.POHeader.FirstOrDefault();

if (POHed_Row != null)
{

  var PORel_Row = Db.PORel.With(LockHint.UpdLock).Where(x => x.Company == Session.CompanyID && x.PONum == POHed_Row.PONum).FirstOrDefault();
  
  if(PORel_Row != null)
  {
  
    if(POHed_Row.Approve)
    {
  
      PORel_Row.LockQty = true;
    
    } else
    {
    
      PORel_Row.LockQty = false;
      
    }
    
  }

}

You’re only grabbing a single row (FirstOrDefault) with that code.
Do a foreach.

foreach(var PORels in(
   from PORel_Row in Db.PORel.With(LockHint.UpdLock)  
   where PORel_Row.Company == Session.CompanyID &&
              PORel_Row.PONum == POHed_Row.PONum
   select PORel_Row))
using( var scope = IceDataContext.CreateDefaultTransactionScope() )
{
    if( PORels != null && POHed_Row.Approve )
    {
        PORels.LockQty = true;
    }
    scope.Complete();
}

Modify that to your needs as I typed that in here and did not syntax check it ¯\_(ツ)_/¯ , but that should give you a direction.

1 Like

I appreciate your response!

My thought process was that I only wanted a single PO Header and then the lambda function was filtering through the PO Releases

var PORel_Row = Db.PORel.With(LockHint.UpdLock).Where(x => x.Company == Session.CompanyID && x.PONum == POHed_Row.PONum).FirstOrDefault();

When we worked with the consultant in training, we created a BPM that updates the Supplier Price List with the updated cost upon receiving the item(s) in inventory. The code that we wrote is:

var receiptLine_Row =  ds.RcvDtl.Where(x=> x.Received == true).FirstOrDefault();

if(receiptLine_Row != null)
{

   var VendPart_Row = Db.VendPart.With(LockHint.UpdLock).Where(x=> x.Company == Session.CompanyID && x.VendorNum == receiptLine_Row.VendorNum && x.PartNum == receiptLine_Row.PartNum).FirstOrDefault();
   
   if(VendPart_Row != null)
   {
   
       VendPart_Row.BaseUnitPrice = receiptLine_Row.DocVendorUnitCost;       
   }
   
   Db.Validate();

}

My understanding was that line 1 was looping through each PO/Pack being received and then looping again through each line item being received (var VendPart_Row) and then assigning the price to the price list.

It sounds like i’m wrong in that thinking.

Somewhat apples-to-oranges there.
Each receipt line has one vendor part you’re looking up (one-to-one).
For the POHeader to PORel, it’s a one-to-many, so you grab your single POHeader row, then you need to loop through all of the PO Releases associated with that header row.

2 Likes

I did some different solutions from what @hmwillett did… i burried the condition of the LockQty != POHed Approve right into the query… so if the query doesnt return anything, you dont do anything… but if you do, you also dont need the if statement… just fix it.
I also moved the scop statement outside the foreach to make it more efficient.
Also changed the query style to the lambda style, and did the query into a variable PORels so that you can check for null before doing all the work.

var POHed_Row = ds.POHeader.FirstOrDefault();
if (POHed_Row != null) {
    var PORels = Db.PORel.With(LockHint.UpdLock).Where(x=>
        x.Company == Session.CompanyID &&
        x.PONum == POHed_Row.PONum && 
        x.LockQty != POHed_Row.Approve);

    if (PORels != null) {
        using( var scope = IceDataContext.CreateDefaultTransactionScope() ){
            foreach (var PORel in PORels){
                PORels.LockQty = POHed_Row.Approve;
            }
            scope.Complete();
        }
    }
}
3 Likes

Hi Dakota,

We went live in JAN with lock QTY on the PO rel but it resulted in us over ordering parts so we got rid of that quickly:

1 Like

Another question, How did you know about the IceDataContext.CreateDefaultTransactionScope()? I’ve been scouring trying to find documentation around Epicor-specific Methods/Enums/etc. and have come up with nothing. The only thing the consultants could help with was giving me the BPM training manual but that shows a very limited scope.

I feel like there is so much built into this that i’m clueless trying to build tools within the environment.

That’s really interesting and I actually forwarded the thread further up management to see if they change their minds on the idea or if they would like to try it. You just toggle the enabled box correct?

I’m tinkering with this solution and the issue i’m having says that I need to reference the System.Transactions.Local in the Assemblies. I see System.Transactions is in the Standard library but when i go to add it to the assemblies, I’m unable to find the reference within the search as it comes up blank.

I have not heard of the IceDataContext object. Could you tell me more about it and what the object does?

Thank you for your help sir!

This forum, mostly.

It’s the same idea as a SQL Transaction block such that if something fails, it will roll back the transaction (if my understanding is correct). @josecgomez will probably call me out and correct me on that. :rofl:

@dakhit If you go to EpicWeb and search on either ABL or C# there are guides that helped move all of us from ABL Epicor 9 to C# in 10 and those basics are what you need. If you have been a C# programmer you will need to rethink some approaches as this is all inside the envelope of Epicor now.

On your original question, I set lockqty on every PO with a data directive and no conditions and have for a decade. The thing you have to watch is that Epicor will make a new rather than a change suggest for the locked PO and a suggestion to kill the old release. Epicor will assume you did the removal of the old release as they told you to. I have a routine that runs on the buyer’s workbench to color rows that we have enough on order just not the right dates so the buyers knows this suggestion is a date change rather than a new buy.

In past life I was largely into the Javascript environment before briefly jumping into Java for a bit and then finally landing in C#. It has all been ‘for fun’ experience as I am a Safety Manager at my company but we are small and I have the most technical experience with programming (mainly web-based). I’ve made a few desktop apps in C#/XAML but struggle finding a way to host a developed C# application without paying. I mainly wanted portfolio experience rather than trying to make a buck off of something. I appreciate the pointer for that and will bookmark it for future reference!

I’m reading a few articles where it sounds like this decision may bite us down the road a bit…I reached out to upper management to ensure this is the route they want to go.

We went live with a BPM to enable lock QTY & RELEASE on PO generation. Within 8 weeks we discovered an issue with us over-ordering. I wasn’t able to pinpoint precisely due to a heavy post go-live workload but we had enough evidence of the over-ordering to remove the BPM and produce a DMT to remove from open POs. There is one more thread on this forum that clearing states this as well which I will try to locate.

This could be the thread:

Also from my own environment. There is now way we should see a PO suggestion for this part. I’m assuming MRP could not modify the open POs as they were locked at the time and suggested a new one as it could manage that.

1 Like

It is an interesting thing… many people think that the LOCK PO is something that they should do or want to do, but by doing this, you are telling MRP that this PO is not able to be changed in any way… but also, MRP is programmed to attempt to “fix” things by ordering more parts if needed… for example, lets say you have a part with a 10 day leadtime… and you have a Demand AND PO scheduled out 30 days into the future. THEN lets say that your demand date changes to 20 days into the future. Now you will not be meeting that demand.

  • If the PO is NOT locked, the the system will simply suggest to change the PO date
  • If the PO is LOCKED, then the system cannot change it. BUT it sees that there is a need 20 days away, and must try to fix this. Since the leadtime is 10 days, it will simply suggest to purchase more, and create a new PO. Your old PO CANNOT be cancelled because it is locked (per the lock rule).
    SO>… Locking is not the correct answer.
3 Likes