Has anyone implemented an approval system for quantity adjustments? We are looking to see what others have done in this case.
We want anyone with access to quantity adjustment to be able to create an adjustment, but this will not actually go through until approval by a manager.
Can we utilize any other tables to store the values from quantity adjustment which will then be used for verification? Thoughts on possibly utilizing cycle count and simply create ‘WHERE’ clauses on the grid to not display our quantity adjustments within cycle count applications and vice versa for quantity adjustment on a new application for this? With this, I am thinking we can use the same table but have not yet tried fully implementing
For approval, I have created a function that, when passing userID and sec code, will return true or false if the user is accepted or denied. The only thing left is finding a table that I can ‘hijack’ for this.
There are a lot of empty UD tables you can use for this. They all have 5 key fields that are strings, and a bunch of other field types for you to use. You could, in theory, create a separate form to show the same information as the quantity adjust screen. Then when the user attempts to make the adjustment, it gets saved into your UD table. Your approver would then use the same form to submit the actual adjustments to the system.
The difficult part here will be reproducing the functionality of the Quantity Adjust screens. If you can replicate those fields, then you should be able to save the requested adjustment into the UD table, and recall those values when the approver comes in to make the actual change. If you are really clever, you can setup the form to make the quantity adjustment without needing to actually go into the QA form.
You can check out your UD tables in a BAQ. Look at UD01 to start and see if there is any data in it. There are a bunch of other UD tables. Find one that is not in use, then create a UBAQ to populate that table. You may also be able to populate it with a function instead.
Looks like UD02 was completely free - not sure what UD01 was being used for but definitely populated.
I’ll post my final solution here
I will be creating data rules to hide and show quantity adjustment OR our new quantity adjustment + approval dependent on security code
Code for security check if anyone is curious or doing something similar on their end. Inputs = company, seccode, userID
try
{
CallService<Ice.Contracts.SecuritySvcContract>(svc =>
{
var result = svc.SecurityGetByID(company, secCode);
if (result?.Security?.Count > 0)
{
var security = result.Security[0];
// Cleaner helper
Func<string, string> clean = s => (s ?? string.Empty)
.Replace(" ", "")
.Replace("/", "")
.Trim('_')
.Trim();
var allowAccessList = (security.AllowAccess ?? string.Empty)
.Split(new[] { '~' }, StringSplitOptions.RemoveEmptyEntries)
.Select(clean)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
var disallowAccessList = (security.DisallowAccess ?? string.Empty)
.Split(new[] { '~' }, StringSplitOptions.RemoveEmptyEntries)
.Select(clean)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
tableOut = string.Join(", ", allowAccessList);
outAccess = false;
CallService<Erp.Contracts.UserFileSvcContract>(userSvc =>
{
try
{
var userResult = userSvc.GetByID(inputUserID);
if (userResult?.UserFile?.Count > 0)
{
var user = userResult.UserFile[0];
var userName = clean(user.Name);
// 1. Security Manager always granted
if (user.SecurityMgr == true)
{
outAccess = true;
debug = $"Access granted: User '{userName}' is a Security Manager.";
return;
}
// 2. Deny if explicitly disallowed by name
if (disallowAccessList.Contains(userName, StringComparer.OrdinalIgnoreCase))
{
outAccess = false;
debug = $"Access denied: User '{userName}' is in DisallowAccess list.";
return;
}
// 3. Allow if explicitly allowed by name
if (allowAccessList.Contains(userName, StringComparer.OrdinalIgnoreCase))
{
outAccess = true;
debug = $"Access granted: User '{userName}' is explicitly in AllowAccess list.";
return;
}
// Get user groups
var groupCodes = (user.GroupList ?? string.Empty)
.Split(new[] { '~' }, StringSplitOptions.RemoveEmptyEntries)
.Select(clean)
.ToList();
var groupDescs = new List<string>();
CallService<Ice.Contracts.SecGroupSvcContract>(groupSvc =>
{
foreach (var code in groupCodes)
{
try
{
var groupResult = groupSvc.GetByID(code);
if (groupResult?.SecGroup?.Count > 0)
{
var desc = clean(groupResult.SecGroup[0].SecGroupDesc);
if (!string.IsNullOrWhiteSpace(desc))
groupDescs.Add(desc);
}
}
catch
{
// Skip if group ID fails
}
}
});
// 4. AllowAll: everyone allowed unless group is disallowed
if (security.AllowAll == true)
{
if (groupDescs.Any(g => disallowAccessList.Contains(g, StringComparer.OrdinalIgnoreCase)))
{
outAccess = false;
debug = $"Access denied: Group(s) ({string.Join(", ", groupDescs)}) are in DisallowAccess, and AllowAll is true.";
return;
}
outAccess = true;
debug = $"Access granted: AllowAll is true.";
return;
}
// 5. DisallowAll: only allow if group or name matches AllowAccess (name already checked above)
if (security.DisallowAll == true)
{
bool groupMatch = groupDescs.Any(g => allowAccessList.Contains(g, StringComparer.OrdinalIgnoreCase));
if (groupMatch)
{
outAccess = true;
debug = $"Access granted: Group matched in AllowAccess under DisallowAll rule.";
return;
}
outAccess = false;
debug = $"Access denied: DisallowAll is true and no group match.";
return;
}
// 6. Standard group match
bool match = allowAccessList.Contains("*") ||
groupDescs.Any(g => allowAccessList.Contains(g, StringComparer.OrdinalIgnoreCase));
if (match)
{
outAccess = true;
debug = $"Access granted: Group(s) matched in AllowAccess.";
}
else
{
outAccess = false;
debug = $"Access denied: No matching name or group found.";
}
}
else
{
debug = $"User '{inputUserID}' not found in UserFile.";
outAccess = false;
}
}
catch (Exception userEx)
{
debug = $"Error in UserFileSvc: {userEx.Message}";
outAccess = false;
}
});
}
else
{
outAccess = false;
tableOut = string.Empty;
debug = $"Security code '{secCode}' not found in company '{company}'.";
}
});
}
catch (Exception ex)
{
outAccess = false;
tableOut = string.Empty;
debug = $"Main error: {ex.Message}";
}