Hey All,
First time posting! Hope to get some feedback from you more seasoned folks. We are wanting to automate the receipt to salvage when labor is posted. I have developed a quick Function and BPMs to accomplish this and wanted to know if there were any ‘got yas’ I didnt think of.
From a high level, BPM triggers on Labor.Update
- Pre-Process gathers initial values and stores in BPM context fields
- Post-Process pulls in context values and triggers the Function.
- Function calculates the salvage amount and cost
- Then runs the PreUpdate and ReceiptSalvage methods.
Pre-Process Code:
bool debug = true;
// Only care about updated rows, A is in here for manual labor adds. MES will create a labordtl record on start production, so all labor activities should be U.
var laborRows = (from ld in ds.LaborDtl
where ld.RowMod == “U” || ld.RowMod == “A”
select ld).ToList();
if (!laborRows.Any())
{
return;
}
// Assume one LaborDtl per call (MES).
var tt = laborRows.FirstOrDefault();
string company = tt.Company;
string jobNum = tt.JobNum;
int asmSeq = tt.AssemblySeq;
int oprSeq = tt.OprSeq;
// NEW values from dataset
decimal newLaborQty = tt.LaborQty;
decimal newScrapQty = tt.ScrapQty;
decimal newDiscrepQty = tt.DiscrepQty;
// OLD values from DB
var existing = (from x in Db.LaborDtl
where x.Company == tt.Company
&& x.LaborHedSeq == tt.LaborHedSeq
&& x.LaborDtlSeq == tt.LaborDtlSeq
select x).FirstOrDefault();
decimal oldLaborQty = existing != null ? existing.LaborQty : 0m;
decimal oldScrapQty = existing != null ? existing.ScrapQty : 0m;
decimal oldDiscrepQty = existing != null ? existing.DiscrepQty : 0m;
// DELTAS
decimal deltaLaborQty = newLaborQty - oldLaborQty;
decimal deltaScrapQty = newScrapQty - oldScrapQty;
decimal deltaDiscrepQty = newDiscrepQty - oldDiscrepQty;
// Total delta driving salvage
decimal operDelta = deltaLaborQty + deltaScrapQty + deltaDiscrepQty;
// If nothing increased, no new salvage should be created
if (operDelta == 0m)
{
// Clear BPM data just in case
this.callContextBpmData.Number01 = 0m;
this.callContextBpmData.Number02 = 0m;
this.callContextBpmData.Number03 = 0m;
this.callContextBpmData.Character01 = string.Empty;
this.callContextBpmData.Character02 = string.Empty;
this.callContextBpmData.Character03 = string.Empty;
this.callContextBpmData.Character04 = string.Empty;
return;
}
// Store deltas & keys in callContextBpmData so Post-Process can see them
this.callContextBpmData.Number01 = deltaLaborQty;
this.callContextBpmData.Number02 = deltaScrapQty;
this.callContextBpmData.Number03 = deltaDiscrepQty;
// Save key fields too so Post has them without re-deriving
this.callContextBpmData.Character01 = company;
this.callContextBpmData.Character02 = jobNum;
this.callContextBpmData.Character03 = asmSeq.ToString();
this.callContextBpmData.Character04 = oprSeq.ToString();
// Optional debug
if (debug)
{
this.PublishInfoMessage(
$“Delta for salvage: Labor={deltaLaborQty}, Scrap={deltaScrapQty}, Discrep={deltaDiscrepQty}, Total={operDelta}”,
Ice.Common.BusinessObjectMessageType.Information,
Ice.Bpm.InfoMessageDisplayMode.Individual,
“”,
“”
);
}
Function Code:
// Parameters: Company, JobNum, AssemblySeq, OprSeq, LaborQty, ScrapQty, DiscrepQty
// Dataset variable: rfmDS (Erp.Tablesets.ReceiptsFromMfgTableset)
// Ensure the dataset exists
if (rfmDS == null)
{
rfmDS = new Erp.Tablesets.ReceiptsFromMfgTableset();
}
// Total pieces processed at this operation = good + scrap + discrep
decimal operQty = LaborQty + ScrapQty + DiscrepQty;
// Basic guards
if (string.IsNullOrEmpty(JobNum))
{
return;
}
// If you do NOT want salvage for net-zero ops, you can do:
if (operQty == 0m) return;
// --- Validate Job ---
var jh = (from j in Db.JobHead
where j.Company == Company
&& j.JobNum == JobNum
select j).FirstOrDefault();
if (jh == null || !jh.JobFirm || jh.JobClosed || !jh.JobReleased)
{
return;
}
// --- Find JobMtl salvage candidates (ZZ material with SalvagePartNum) ---
var jobMtls = (from m in Db.JobMtl
where m.Company == Company
&& m.JobNum == JobNum
&& m.PartNum.StartsWith("ZZ")
&& m.SalvagePartNum != ""
// optional: only materials tied to this operation
&& (m.RelatedOperation == OprSeq) // || m.RelatedOperation == 0
select m).ToList();
if (!jobMtls.Any())
{
return;
}
foreach (var jm in jobMtls)
{
decimal salvagePer = (decimal)jm.SalvageQtyPer;
decimal mltQtyPer = (decimal)jm.QtyPer;
if (salvagePer == 0m || mltQtyPer == 0m)
continue;
// Salvage for this op = (good + scrap + discrep) × material qty per FG × salvage qty per material
decimal salvageThisOperation = operQty * mltQtyPer * salvagePer;
decimal salvageQty = salvageThisOperation;
if (salvageQty == 0m)
continue;
// Get salvage part (for UOM and basic validation)
var salvagePart = (from sp in Db.Part
where sp.Company == Company
&& sp.PartNum == jm.SalvagePartNum
select sp).FirstOrDefault();
if (salvagePart == null)
continue;
string salvageUM = salvagePart.IUM;
string salvageWarehouse = "Default"; // TODO: your salvage WH
string salvageBin = "Salv"; // TODO: your salvage bin
// ----------------------------------------------------------------------
// Compute salvageUnitCost from ICommCode + PartUOM (no _UD join)
// ----------------------------------------------------------------------
decimal salvageUnitCost = 0m;
// Get the ZZ material part on the job (jm.PartNum)
var zzMatPart = (from p in Db.Part
where p.Company == Company
&& p.PartNum == jm.PartNum
select p).FirstOrDefault();
if (zzMatPart != null)
{
// Commodity for that material - Custom Table
var comm = (from c in Db.ICommCode
where c.Company == Company
&& c.CommodityCode == zzMatPart.CommodityCode
select c).FirstOrDefault();
if (comm != null)
{
decimal salvagePricePerPound = comm.SalvagePricePerPound_c;
decimal buyingRebatePerPound = comm.BuyingRebatePerPound_c;
decimal sellingRebatePerPound = comm.SellingRebatePerPound_c;
var uom = (from u in Db.PartUOM
where u.Company == Company
&& u.PartNum == jm.PartNum
select u).FirstOrDefault();
decimal convFactor = 1m;
if (uom != null)
convFactor = (decimal)uom.ConvFactor;
decimal salvageUnitCredit = (salvagePricePerPound + sellingRebatePerPound + buyingRebatePerPound) * convFactor;
salvageUnitCost = salvageUnitCredit;
}
}
// If you only want salvage when you have a positive credit, you can optionally do:
// if (salvageUnitCost <= 0m)
// continue;
// --- Create a PartTran row in rfmDS ---
var pt = rfmDS.PartTran.NewRow();
var now = DateTime.Now;
int sysTime = now.Hour * 3600 + now.Minute * 60 + now.Second;
pt["Company"] = Company;
pt["Plant"] = jh.Plant;
pt["Plant2"] = jh.Plant;
pt["TranClass"] = "R";
pt["TranType"] = "SVG-STK";
pt["InventoryTrans"] = true;
pt["TranDate"] = now.Date;
pt["SysDate"] = now.Date;
pt["SysTime"] = sysTime;
pt["JobNum"] = jm.JobNum;
pt["AssemblySeq"] = jm.AssemblySeq;
pt["JobSeqType"] = "M";
pt["JobSeq"] = jm.RelatedOperation != 0 ? jm.RelatedOperation : OprSeq;
pt["PartNum"] = jm.SalvagePartNum;
pt["TranQty"] = salvageQty;
pt["ActTranQty"] = salvageQty;
pt["UM"] = salvageUM;
pt["ActTransUOM"] = salvageUM;
pt["DimConvFactor"] = 1m;
pt["WareHouseCode"] = salvageWarehouse;
pt["BinNum"] = salvageBin;
pt["MtlUnitCost"] = salvageUnitCost;
pt["MtlMtlUnitCost"] = salvageUnitCost;
pt["BegMtlUnitCost"] = salvageUnitCost;
pt["ExtCost"] = Math.Round(salvageQty * salvageUnitCost, 4);
pt["GLTrans"] = true;
pt["RowMod"] = "A";
// Add the row to PartTran (use Add, not Rows.Add)
rfmDS.PartTran.Add(pt);
}
