Hello Everyone,
Recently I was working with the Acumatica Barcode-Driven Engine, one thing I could not find is how to implement a custom ScanQuestion into an existing form. So, I thought I would share a way that I got mine to work, if anyone else has any approaches that worked for them, please share them!
Firstly, you want to create an extension to the scan functionality. You don’t do this through inheriting the traditional PXGraphExtension<Graph> , but rather by inheriting the ScanGraph.ScanExtension something like this:
public class AIWMS_INScanReceiveExt : INScanReceive.ScanExtension
{
}
Secondly, you need to define the ScanQuestion class, which inherits either ScanGraph.ScanQuestion or ScanQuestion<ScanGraph>
public class DefaultLocationQuestion : ScanQuestion<INScanReceive>
{
}Inheriting this class requires the implementation of string Code, void Confirm, and void Reject as protected overrides. You should also add a localisable message class for the prompt and return it using the GetPrompt() method.
public class DefaultLocationQuestion : ScanQuestion<INScanReceive>
{
[PXLocalizable]
public abstract class Msg
{
public const string Prompt = "Item was not scanned into the default location\n\n" +
"Press Ok to continue, No to cancel";
}
public override string Code => "ALLOWNONDEFAULTLOCATION";
protected override string GetPrompt()
{
return Msg.Prompt;
}
protected override void Confirm()
{
Basis.DispatchNext(null);
}
protected override void Reject()
{
FlowStatus.Fail("The Scan Has Been Cancelled");
}
}After adding the ScanQuestion you should add it to the required mode, in this case the ReceiptMode, by overriding the DecorateScanMode() method.
[PXOverride]
public ScanMode<INScanReceive> DecorateScanMode(
ScanMode<INScanReceive> original,
Func<ScanMode<INScanReceive>, ScanMode<INScanReceive>> baseMethod)
{
//Get the current ScanMode.
var mode = baseMethod(original);
//Return your modified mode.
return mode;
}Then we need to intercept the CreateQuestions() method to add our question into the state.
[PXOverride]
public ScanMode<INScanReceive> DecorateScanMode(
ScanMode<INScanReceive> original,
Func<ScanMode<INScanReceive>, ScanMode<INScanReceive>> baseMethod)
{
var mode = baseMethod(original);
mode.Intercept //Use intercept to add your logic into the current method execution.
.CreateQuestions //Select the method you want to intercept
.ByAppend((basis) => //Select how you want to intercept the method, you can do this ByOverride, ByAppend, ByPrepend, ByReplace, or ByBaseSubstitute.
{
return new[] { new DefaultLocationQuestion() };
});
return mode;
}Now, onto the part which I found most difficult whilst implementing this.
The barcode-driven engine uses Basis.Ask/Warn<TScanQuestion>( string message, object params[]) to implement a ScanQuestion, however, I could not get this to work. How it does work for me is through overriding the Confirmation State of the ScanMode. You do this through overriding the DecorateScanState() method, something like this:
[PXOverride]
public ScanState<INScanReceive> DecorateScanState(
ScanState<INScanReceive> original,
Func<ScanState<INScanReceive>, ScanState<INScanReceive>> baseMethod)
{
PXTrace.WriteInformation("State Override Successful"); //Inform the state override has been performed.
var state = baseMethod(original); //Get the current state of the barcode engine (this works similar to the Workflow API)
if (state is ReceiptMode.ConfirmState confirm) //Check whether the current state is ConfirmState
{
if(Basis.Header.PrevScanState != "QTY") //In my case, I didn't want the question to trigger if the last state of the barcode engine was the QtyState.
{
confirm.Intercept
.PerformConfirmation
.ByOverride((basis, defaultPerformer) =>
{
var confirmation = defaultPerformer(); //Perform the default confirmation method.
InventoryItemCurySettings item = SelectFrom<InventoryItemCurySettings>.
Where<InventoryItemCurySettings.inventoryID.IsEqual<P.AsInt>>.View.Select(Basis, Basis.InventoryID); //Find the current item settings record.
if (item == null)
// Acuminator disable once PX1050 HardcodedStringInLocalizationMethod [Justification]
throw new PXException("No Inventory Item Found, Please Reset Scan");
INLocation location = SelectFrom<INLocation>.
Where<INLocation.locationID.IsEqual<P.AsInt>>.View.Select(Basis, Basis.LocationID); //Find the current location record.
if (location == null)
// Acuminator disable once PX1050 HardcodedStringInLocalizationMethod [Justification]
throw new PXException("No Location Found, Please Reset Scan");
AIWMS_INLocationExt locationExt = location.GetExtension<AIWMS_INLocationExt>(); //Get the CacheExtension for the current location record.
if (locationExt.UsrSuppressWarning != true && Basis.LocationID != item.DfltReceiptLocationID) //Check whether the item is being receipted into the item's default location.
return FlowStatus.Warn<DefaultLocationQuestion>("Item Was Not Scanned To Default Location."); //Report a warning and require an answer if the item is not being receipted into the default location.
return FlowStatus.Ok.WithDispatchNext;
});
}
return state;
}
return state;
}Once you have entered this, either publish your customisation, or compile your .dll and wait for the solution to refresh, after entering your InventoryID scan within the barcode engine you will be asked whether you are sure you want to receipt this item into its non-default location.
Hope this helps someone!
Aleks