Support still insists that everything is Working As Intended™.
All the bugs I’ve identified can be fixed or prevented with a single, relatively simple in-transaction data directive on UOMConv. Add references to Erp.Contracts.BO.Part and Erp.Contracts.BO.UOMClass.
I just finished testing each of the 9 scenarios identified in the comment, and this DD successfully prevents all of them. Item 8 actually prevents you from setting up a test of item 7. This is a Good Thing! But I had some Parts that were already in an inconsistent state that allowed me to fully test 7. Ideally you’d want to create this DD before creating any UOMs or Parts. But if your system is already corrupted, this should at least keep it from getting any worse.
/*
1. [bug fix] Prevent user from deleting UOMConv referenced by Part.
2. [bug fix] Prevent user from deleting UOMConv if related PartUOM is marked used.
3. [bug fix] Prevent user from unchecking PartSpecific if related PartUOM is marked used.
4. [bug fix] Delete related PartUOMs if deleting UOMConv is allowed.
5. [bug fix] Delete related PartUOMs if unchecking PartSpecific is allowed.
6. [bug fix] Prevent user from checking PartSpecific if UOMConv is marked used.
7. [bug fix] Prevent Epicor from marking part-specific UOMConv used when PartUOM is missing.
8. [best practice] Prevent user from creating a part-specific UOMConv with a non-zero factor.
9. [best practice] Prevent user from creating a part-specific UOMConv in a built-in class.
*/
foreach(var row in ttUOMConv.Where(c => c.Added() || c.Updated()))
{
/* 6 and 7. Could differentiate error messages, but the result would be the same. */
if(row.HasBeenUsed && row.PartSpecific)
{
throw new Ice.BLException("HasBeenUsed and PartSpecific are mutually exclusive.");
}
/* 8 and indirectly 7. */
if(row.PartSpecific && row.ConvFactor != 0)
{
throw new Ice.BLException("PartSpecific requires a ConvFactor of zero.");
}
}
/* Identify updated rows where PartSpecific changed. */
var psChanged = ttUOMConv.Where(up => up.Updated() && ttUOMConv.Single(un => un.Unchanged() && un.SysRowID == up.SysRowID).PartSpecific != up.PartSpecific);
foreach(var row in ttUOMConv.Where(c => c.Added() && c.PartSpecific).Union(psChanged.Where(o => o.PartSpecific)))
{
CallService<Erp.Contracts.UOMClassSvcContract>(svc =>
{
var ds = svc.GetByID(row.UOMClassID);
/* 9. */
if(ds.UOMClass.Single().ClassType != "Other")
{
throw new Ice.BLException("PartSpecific only allowed in class type Other.");
}
});
}
foreach(var row in ttUOMConv.Where(c => c.Deleted()).Union(psChanged.Where(o => !o.PartSpecific)))
{
CallService<Erp.Contracts.PartSvcContract>(svc =>
{
/* Page through Parts to minimize memory footprint. Requires validation pass and update pass. */
var pageSize = 10;
var absolutePage = 0;
var morePages = false;
var partUomsExist = false;
do
{
var ds = svc.GetRows(
$"UOMClassID = '{row.UOMClassID}'", /* Part */
"0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1",
$"UOMCode = '{row.UOMCode}'", /* PartUOM */
"0=1", "0=1",
pageSize,
++absolutePage,
out morePages
);
/* 1. */
if(row.Deleted() && ds.Part.Any(o => o.IUM == row.UOMCode || o.PUM == row.UOMCode || o.SalesUM == row.UOMCode))
{
throw new Ice.BLException("Cannot delete UOMConv referenced by Part.");
}
/* 2 and 3. */
if(ds.PartUOM.Any(o => o.HasBeenUsed))
{
var action = row.Deleted() ? "delete UOMConv" : "uncheck PartSpecific";
throw new Ice.BLException($"Cannot {action} after related PartUOM HasBeenUsed.");
}
partUomsExist |= ds.PartUOM.Any();
}
while(morePages);
/* Update pass could race with invalidating changes to PartUOMs. */
if(partUomsExist)
{
absolutePage = 0;
do
{
var ds = svc.GetRows(
$"UOMClassID = '{row.UOMClassID}'", /* Part */
"0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1", "0=1",
$"UOMCode = '{row.UOMCode}'", /* PartUOM */
"0=1", "0=1",
pageSize,
++absolutePage,
out morePages
);
if(ds.PartUOM.Any())
{
/* 4 and 5. */
foreach(var pu in ds.PartUOM)
{
pu.RowMod = "D";
}
svc.Update(ref ds);
}
}
while(morePages);
}
});
}