I’ve been asked to create a BPM that will show a message to contact the buyer if the cost of a PO received is >= or <= $1000 + STD cost.
I can do this in SQL (not sure if RcvDtl.OurUnitCost is the correct column to compare) but I’m not sure how to use this in a BPM. Anyone have an idea of how to implement this?
SELECT
rd.ponum,
rd.poline,
rd.partnum,
SUM(pc.StdBurdenCost + pc.StdLaborCost + pc.StdMaterialCost + pc.StdMtlBurCost + pc.StdSubContCost) as 'STD Cost',
rd.OurUnitCost,
SUM(pc.StdBurdenCost + pc.StdLaborCost + pc.StdMaterialCost + pc.StdMtlBurCost + pc.StdSubContCost) - rd.OurUnitCost AS 'Cost Difference'
FROM
RcvDtl rd
JOIN
partcost pc ON pc.company = rd.company AND pc.partnum = rd.partnum
GROUP BY
rd.ponum, rd.poline, pc.StdBurdenCost, pc.StdLaborCost, pc.StdMaterialCost, pc.StdMtlBurCost, pc.StdSubContCost, rd.partnum, rd.OurUnitCost
HAVING
SUM(pc.StdBurdenCost + pc.StdLaborCost + pc.StdMaterialCost + pc.StdMtlBurCost + pc.StdSubContCost) >= rd.OurUnitCost + 1000
OR
SUM(pc.StdBurdenCost + pc.StdLaborCost + pc.StdMaterialCost + pc.StdMtlBurCost + pc.StdSubContCost) <= rd.OurUnitCost - 1000
I do this on PartTran, which is… kind of evil. Not great for performance and all that. Also I made the BPM in the dark ages before Epicor Functions. Setting aside my poor implementation, though…
Are you wanting a Data Directive BPM (on the table) or a Method Directive (on an action)?
a. If Method, be advised that mass receipt is different from single receipt.
I do recommend using an Epicor Function to get the hang of the math, etc. Then wire that into your BPM.
“The” Standard Cost is not a thing. You have to ping the Plant table to get the active CostID for that site. I get it, today you only have one. But you won’t always…
Are you intending this for expense (and MRO) items, too? If so, that’s fine. But they don’t really obey standard cost, so you may or may not care in those cases.
Also, it seems that my BPM could only handle 2-decimal precision, which gets annoying sometimes. But you’re not worried about that level of detail. Just wanted to mention that. (I don’t remember the exact point that it does that. One day I’ll fix it. Sure.)
I believe a method would be best, no? I’ve read not to touch data directives unless you really have to.
I haven’t worked with Epicor functions at all but I will look into this approach.
I was kind of wondering if this is something that should be done in a BPM or maybe we should look at the process instead. Just thinking back to what happened with raw material prices in 2020, if something similar were to happen that may generate some irritation with purchasers.
We are already multi-plant so I am glad I took the time to ask rather than try and
hack something together as I would not have thought to check the plant table.
Right, method directives are proper. Just know that you’ll have to make 2 BPMs at least - one for Mass Receipt and one for a single line (whatever that’s called). And the mass receipt, of course, is a dataset with many rows, so somehow you have to get the cost of each row and compare it to the PO price on each row.
I was lazy/scared and just did a Data Directive.
Meh. Normally, that’s the answer. But ordinary users just don’t care about some arbitrary number defined by accounting. (I do care; I’m just saying.)
Maybe I should compare the cost of the PO to STD cost when purchasers are creating the PO instead of waiting for the receipt to happen since it will get kicked back to purchasing anyway.
I think I know what I want to do I just don’t know how to best accomplish it. I was thinking to use code that retrieves STD cost, stores that as a variable then do the same for the PO and then perform some math, but this requires me to learn more about C#, which my knowledge is rudimentary at best.
This is two bpms that you may combine into the data directive that does the check for over 1000 variance and sends the email. This is an inhouse email so it is not fancy.
On PO Update require reason.
/* reason required.*/
object MESSAGE_ERR = null;
Erp.Tables.PartCost PartCost;
foreach (var ttPODetailRow in (from ttPODetail_Row in ds.PODetail
where ttPODetail_Row.Updated() select ttPODetail_Row))
{
var reason = ttPODetailRow["ShortChar01"].ToString();
Ice.Diagnostics.Log.WriteEntry(" PO Reason >>" + reason + "<<");
if(String.IsNullOrEmpty(reason))
{
var stdmtlCost = Db.PartCost.Where(PartCost_Row=> PartCost_Row.Company == ttPODetailRow.Company && PartCost_Row.PartNum == ttPODetailRow.PartNum).Select(PartCost_Row=> PartCost_Row.StdMaterialCost).FirstOrDefault();
if (stdmtlCost != null)
{
if ((ttPODetailRow.UnitCost > (stdmtlCost * 1.01m)) && String.IsNullOrEmpty(reason))
{
CallContext.Current.ExceptionManager.AddBLException("PO Reason is required for this line over std");
}
if (ttPODetailRow.UnitCost < (stdmtlCost - (stdmtlCost * .01m)) && String.IsNullOrEmpty(reason))
{
string Msg = string.Empty;
if (this.callContextClient.CurrentUserId == "me")
{
Msg = " cost " + System.Convert.ToString(ttPODetailRow.UnitCost) + " less than " + System.Convert.ToString(stdmtlCost - (stdmtlCost * .01m)) + "PO Reason is required for this line under std";
}
else
{
Msg = "PO Reason is required for this line under std";
}
CallContext.Current.ExceptionManager.AddBLException(Msg);
}
}
}
}
Data Directive on PODetail
Condition so it does not run from Receipt Entry or Inspection Processing.
custom Code in In transaction, but running Async, so you can check the exiting data versus the new data.