Alright, Alright. I just can’t allow you to do Data Directives and Hacks. Especially for my dear friend @gpayne!!!
I’ll give you a solution that supports all the Generate scenarios. Different Buyers, Multiple PO’s etc… Consider this my 2022 gift to the community, it wasn’t easy. Might even have some tech debt in it.
A Single Method Directive to rule them all…
Basically we added Project ID, LockQty and LockDate columns to PO Suggestion Entry and then pass it down to PORel and ProjectID to POHead.
Erp.BO.POSugg.Generate POST Directive
Example shows changing applying columns to releases and 1 to header, but the code is written in a way to easily add changes to line as well.
- Supports all scenarios such as when PO Suggestion generate a Single PO, Multiple PO’s, Multiple Lines, Multiple Release etc…
- Supports PartClass Split Setting.
- Supports Appending New Lines / Rels to Existing PO
// Pass Values to Newly created POs
//
// NOTES:
// Epicor creates a new PO when any of the following changes:
// Grouping Algorithm:
//
// New POHead GroupBy = BuyerID, VendorNum, PurPoint, ShipViaCode, TermsCode, FOB
// New PODtl GroupBy = PartNum, IUM, PUM
// New PORel GroupBy = SugNum processed
//
// Helpers
Func<string, string> GetProjectContractID = (sProjectID) =>
{
return Db.Project.Where(x => x.Company == Session.CompanyID && x.ProjectID == sProjectID)
.Select(s => s.ContractID).DefaultIfEmpty("").First();
};
Func<string, bool> IsValidProjectID = (sProjectID) =>
{
return Db.Project.Any(x => x.Company == Session.CompanyID && x.ProjectID == sProjectID);
};
Func<string, bool> IsValidContractID = (sContractID) =>
{
return !string.IsNullOrEmpty(sContractID)
&& Db.PlanContractHdr.Any(x => x.Company == Session.CompanyID && x.ContractID == sContractID && x.Active == true);
};
Func<string, bool> IsPartInMaster = (sPartNum) =>
{
return Db.Part.Any(x => x.Company == Session.CompanyID && x.PartNum == sPartNum);
};
//
// Process Material Suggestions that have a pre-defined PONum
//
// In PO Suggestions you can specify a existing PONum and Line
// This will only append new releases
//
var ttSugPoDtlExistingPONum =
ttSugPoDtl.Where(w => w.SugType == "M" && w.PONUM != 0)
.GroupBy(g => new {
g.PONUM,
g.POLine
}).Select(r => r).ToList();
foreach (var sugRowx in ttSugPoDtlExistingPONum)
{
Ice.Diagnostics.Log.WriteEntry("---------------- Append to Existing PO Detail ---------------");
Ice.Diagnostics.Log.WriteEntry($"[ PODtl ] PONum: {sugRowx.Key.PONUM} - Line: {sugRowx.Key.POLine}");
int iMaxRelNum =
(from por in Db.PORel
where por.Company == Session.CompanyID
&& por.PONum == sugRowx.Key.PONUM
&& por.POLine == sugRowx.Key.POLine
select por.PORelNum
).Max();
//Ice.Diagnostics.Log.WriteEntry("iMaxRelNum: " + iMaxRelNum + " - sugRowCount: " + sugRowx.Count());
int relNum = iMaxRelNum - sugRowx.Count();
foreach (var sugRow in sugRowx)
{
relNum++;
var relRow =
(from por in Db.PORel
where por.Company == Session.CompanyID
&& por.PONum == sugRow.PONUM
&& por.POLine == sugRow.POLine
&& por.PORelNum == relNum
select por
).FirstOrDefault();
//Ice.Diagnostics.Log.WriteEntry("---------------- PORel ---------------");
Ice.Diagnostics.Log.WriteEntry($"[ PORel ] Line: {relRow.POLine} - Rel: {relRow.PORelNum} - DueDate: {relRow.DueDate?.ToShortDateString()} - RelQty: {relRow.RelQty}" +
$" - idxProjectID_c: {sugRow.UDField<string>("idxProjectID_c")} - LockDate_c: {sugRow.UDField<bool>("LockDate_c")} - LockQty_c: {sugRow.UDField<bool>("LockQty_c")}");
using (var txScope = IceContext.CreateDefaultTransactionScope())
{
relRow.LockDate = sugRow.UDField<bool>("LockDate_c");
relRow.LockQty = sugRow.UDField<bool>("LockQty_c");
relRow.ProjectID = sugRow.UDField<string>("idxProjectID_c");
// Mimick Epicor Logic when changing Project ID
// Assign the default Planning Contract from Project. Do Not default if PartNum is not a master-Part
if (!string.IsNullOrEmpty(relRow.ProjectID))
{
string sProjectDefaultContractID = GetProjectContractID(relRow.ProjectID);
if (IsValidContractID(sProjectDefaultContractID) && IsPartInMaster(sugRow.POLinePartNum))
{
relRow.ContractID = sProjectDefaultContractID;
}
}
Db.Validate(relRow);
txScope.Complete();
}
}
}
//
// Process Material Suggestions that have a new PONum
//
// Group Suggestions by the Keys
//
var ttSugPoDtlGrouped =
ttSugPoDtl.Where(w => w.SugType == "M" && w.PONUM == 0)
.GroupBy(g => new {
g.BuyerID,
g.VendorNum,
g.PurPoint,
g.ShipViaCode,
g.TermsCode,
g.FOB
}).Select(r => r).ToList();
int iMaxPONum =
(from ph in Db.POHeader
where ph.Company == Session.CompanyID
select ph.PONum
).Max();
int iNextPONum = iMaxPONum - ttSugPoDtlGrouped.Count() - ttSugPoDtl.Where(w => w.SugType == "S" && w.PONUM == 0).Count();
foreach (var grp in ttSugPoDtlGrouped)
{
iNextPONum++;
Ice.Diagnostics.Log.WriteEntry("---------------- New POHead ---------------");
//Ice.Diagnostics.Log.WriteEntry($"iNextPONum: {iNextPONum}");
// Get the Next POHead that has been created
// We use several keys in the where for more accuracy
var headRow =
(from poh in Db.POHeader
where poh.Company == Session.CompanyID
&& poh.PONum >= iNextPONum
&& poh.BuyerID == grp.Key.BuyerID
&& poh.VendorNum == grp.Key.VendorNum
&& poh.PurPoint == grp.Key.PurPoint
&& poh.TermsCode == grp.Key.TermsCode
&& poh.ShipViaCode == grp.Key.ShipViaCode
&& poh.FOB == grp.Key.FOB
orderby poh.PONum descending
select poh
).FirstOrDefault();
Ice.Diagnostics.Log.WriteEntry($"[ POHead ] PONum: {iNextPONum} - BuyerID: {grp.Key.BuyerID} - TermsCode: {grp.Key.TermsCode} - ShipViaCode: {grp.Key.ShipViaCode}" +
$" - FOB: {grp.Key.FOB} - VendorNum: {grp.Key.VendorNum} - PurPoint: {grp.Key.PurPoint}");
if (headRow == null) {
Ice.Diagnostics.Log.WriteEntry($"[ Error ] PO Not Found! iNextPONum: {iNextPONum}");
return;
}
var grpGroupedByLine = grp.GroupBy(g => new { g.PartNum, g.IUM, g.PUM }).Select(r => r);
int lineNum = 0;
foreach (var sugRowx in grpGroupedByLine)
{
lineNum++;
//Ice.Diagnostics.Log.WriteEntry("---------------- PODetail ---------------");
Ice.Diagnostics.Log.WriteEntry($"Processing Keys... PartNum: {sugRowx.Key.PartNum} - IUM/PUM: {sugRowx.Key.IUM}/{sugRowx.Key.PUM}");
Ice.Diagnostics.Log.WriteEntry($"Looking for... PONum: {headRow.PONum}");
// Get the PO Line related to the suggestion PartNum
// that we are currently processing
// We are currently not writing anything to the POLine but we may in the future
var detailRow =
(from pod in Db.PODetail
where pod.Company == Session.CompanyID
&& pod.PONUM == headRow.PONum
&& pod.PartNum == sugRowx.Key.PartNum
&& pod.IUM == sugRowx.Key.IUM
&& pod.PUM == sugRowx.Key.PUM
select pod
).FirstOrDefault();
Ice.Diagnostics.Log.WriteEntry($"[ PODtl ] Line: {detailRow.POLine} - PartNum: {detailRow.PartNum} - IUM/PUM: {detailRow.IUM}/{detailRow.PUM}" +
$" - DueDate: {detailRow.DueDate?.ToShortDateString()}");
string OldCommentText = string.Empty;
int relNum = 0;
foreach (var sugRow in sugRowx)
{
relNum++;
//
// CHECK FOR SPLIT LOGIC
//
bool isSplit = Db.PartClass.Any(x => x.Company == Session.CompanyID && x.ClassID == sugRow.ClassID && x.SplitPOLine == true);
if (isSplit && relNum > 1 && OldCommentText != sugRow.CommentText)
{
Ice.Diagnostics.Log.WriteEntry("!!!! RELEASE SPLIT DETECTED !!!!");
Ice.Diagnostics.Log.WriteEntry($"CommentText: {sugRow.CommentText} - OldCommentText: {OldCommentText}");
lineNum++;
relNum = 1;
Ice.Diagnostics.Log.WriteEntry($"Fetching: {headRow.PONum} - Line: {lineNum}");
// Overwrite current detailRow
detailRow =
(from pod in Db.PODetail
where pod.Company == Session.CompanyID
&& pod.PONUM == headRow.PONum
&& pod.POLine == lineNum
select pod
).FirstOrDefault();
Ice.Diagnostics.Log.WriteEntry($"After Fetch...");
Ice.Diagnostics.Log.WriteEntry($"[ PODtl ] Line: {detailRow.POLine} - PartNum: {detailRow.PartNum} - IUM/PUM: {detailRow.IUM}/{detailRow.PUM}" +
$" - DueDate: {detailRow.DueDate?.ToShortDateString()}");
}
//
// PO Releases
//
var relRow =
(from por in Db.PORel
where por.Company == Session.CompanyID
&& por.PONum == detailRow.PONUM
&& por.POLine == detailRow.POLine
&& por.PORelNum == relNum
select por
).FirstOrDefault();
//Ice.Diagnostics.Log.WriteEntry("---------------- PORel ---------------");
Ice.Diagnostics.Log.WriteEntry($"[ PORel ] Line: {relRow.POLine} - Rel: {relRow.PORelNum} - DueDate: {relRow.DueDate?.ToShortDateString()} - RelQty: {relRow.RelQty}" +
$" - idxProjectID_c: {sugRow.UDField<string>("idxProjectID_c")} - LockDate_c: {sugRow.UDField<bool>("LockDate_c")} - LockQty_c: {sugRow.UDField<bool>("LockQty_c")} - SugNum: {sugRow.SugNum}");
using (var txScope = IceContext.CreateDefaultTransactionScope())
{
relRow.LockDate = sugRow.UDField<bool>("LockDate_c");
relRow.LockQty = sugRow.UDField<bool>("LockQty_c");
relRow.ProjectID = sugRow.UDField<string>("idxProjectID_c");
// Mimick Epicor Logic when changing Project ID
// Assign the default Planning Contract from Project. Do Not default if PartNum is not a master-Part
if (!string.IsNullOrEmpty(relRow.ProjectID))
{
string sProjectDefaultContractID = GetProjectContractID(relRow.ProjectID);
if (IsValidContractID(sProjectDefaultContractID) && IsPartInMaster(detailRow.PartNum))
{
relRow.ContractID = sProjectDefaultContractID;
}
}
Db.Validate(relRow);
txScope.Complete();
} // using
// Store Comment Text for Split Detection
OldCommentText = sugRow.CommentText;
}
}
//
// Update POHead
// By Finding the Last Project used on a Release
//
string sLastRelProjectID =
(from pr in Db.PORel
where pr.Company == headRow.Company
&& pr.PONum == headRow.PONum
orderby
pr.POLine descending,
pr.PORelNum descending
select pr.ProjectID
).Take(1).DefaultIfEmpty("").First();
if (!string.IsNullOrEmpty(sLastRelProjectID))
{
Ice.Diagnostics.Log.WriteEntry($"Assigning ProjectID to POHead... {sLastRelProjectID}");
using (var txScope = IceContext.CreateDefaultTransactionScope())
{
headRow.idxProjectID_c = sLastRelProjectID;
Db.Validate(headRow);
txScope.Complete();
}
}
}
Enjoy @hmwillett