Extension of Scan Components: To Extend Predefined Scan States for a Custom Form
This activity will walk you through the process of altering predefined scan states.
Story
LocationState
, InventoryItemState
, and
LotSerialState
. You need to modify these states to use them in
your scan mode as follows: - For the
LocationState
scan state, you want to add additional validation that is specific to this current scan mode. - For the
InventoryItemState
scan state, you want to add an additional absence handler that is specific to this scan mode. - You want the
LotSerialState
to be active only when it is already active and when the lot or serial number is entered on receipt.
Process Overview
- You will introduce a descendant for the
LocationState
class. - You will inject the logic when
InventoryItemState
is created. - You will inject the logic when
LotSerialState
is decorated.
System Preparation
Before you begin performing the steps of this activity, do the following:
- Prepare an MYOB Acumatica instance by performing the Test Instance: To Deploy an Instance prerequisite activity.
- Create a barcode scan class by performing the Barcode Scan Class: To Create a Barcode Scan Class prerequisite activity.
- Create the scan mode and define its required properties by performing the Barcode Scan Mode: To Define the Required Properties prerequisite activity.
- Create the set of scan states by performing the Barcode Scan States: To Create the Set of Scan States prerequisite activity.
Step 1: Introducing a Descendant
- In the
CountMode
class, define theLocationState
class, as follows.public class INScanCount : WMSBase { public sealed class CountMode : ScanMode { ... public new sealed class LocationState : WMSBase.LocationState { } ... } }
You use the same name for the descendant that is used for the predefined class. The code of this activity includes two versions of the
LocationState
class: the originalWMSBase.LocationState
class and theCountMode
-relatedINScanCount.CountMode.LocationState
class. Because the original class and the descendant have the same name, you will not need to change theCreateStates()
method. - In the
LocationState
class, implement the IsStateSkippable() method so thatLocationState
is skipped if the location is already selected, as shown in the following code.public new sealed class LocationState : WMSBase.LocationState { protected override bool IsStateSkippable() => Basis.LocationID != null; }
In
CountMode
, you do not need to ask a user for a location again if the location is already selected. Therefore, you implement the IsStateSkippable() method so thatLocationState
is skipped if the location is already selected. - In the INScanCount.cs, add the following
using
directive.using PX.Data.BQL.Fluent;
- In the
LocationState
class, add the Validate() method, as shown in the following code.public new sealed class LocationState : WMSBase.LocationState { protected override bool IsStateSkippable() => Basis.LocationID != null; protected override Validation Validate(INLocation location) { if (Basis.HasFault(location, base.Validate, out Validation fault)) return fault; INPIStatusLoc statusLocation = SelectFrom<INPIStatusLoc>. Where< INPIStatusLoc.pIID.IsEqual<WMSScanHeader.refNbr.FromCurrent>. And< INPIStatusLoc.locationID.IsEqual<@P.AsInt>. Or<INPIStatusLoc.locationID.IsNull>>>. View.ReadOnly .Select(Basis, location.LocationID); if (statusLocation == null) return Validation.Fail(Msg.NotPresent, location.LocationCD); return Validation.Ok; } [PXLocalizable] public new abstract class Msg : WMSBase.LocationState.Msg { public const string NotPresent = "The {0} location is not in the list and cannot be added."; } }
In
CountMode
, you need only locations that are present in the physical inventory that the user is currently processing. Therefore, you add additional validation logic in the Validate() method. You execute the base validation first. Only if the base validation is passed, the custom validation is performed.
Step 2: Injecting the Logic When the Component Is Created
CountMode
class, modify the CreateStates()
method as follows.public class INScanCount : WMSBase
{
public sealed class CountMode : ScanMode
{
...
protected override IEnumerable<ScanState<INScanCount>> CreateStates()
{
yield return new RefNbrState();
yield return new LocationState();
yield return new InventoryItemState()
.Intercept.HandleAbsence.ByAppend((basis, barcode) =>
{
if (basis.TryProcessBy<LocationState>(barcode,
StateSubstitutionRule.KeepPositiveReports |
StateSubstitutionRule.KeepApplication))
return AbsenceHandling.Done;
return AbsenceHandling.Skipped;
});
yield return new LotSerialState();
yield return new ConfirmState();
}
...
}
}
In the CreateStates() method, you intercept the
HandleAbsence method. You use the ByAppend
interception strategy because you want to register an additional absence handler
without affecting the existing ones. In CountMode
, you need the
ability to switch the location even if the current input state is
InventoryItemState
. You also configure the
LocationState
state to try handle the input.
Step 3: Injecting the Logic When the Component Is Decorated
CountMode
class, add the DecorateScanState()
method as follows.public class INScanCount : WMSBase
{
...
protected override ScanState<INScanCount> DecorateScanState(
ScanState<INScanCount> original)
{
var state = base.DecorateScanState(original);
if (state is WMSBase.LotSerialState lotSerialState)
InjectLotSerialDeactivationOnNonEnterableLotSerials(lotSerialState);
return state;
}
protected virtual void InjectLotSerialDeactivationOnNonEnterableLotSerials(
WMSBase.LotSerialState lotSerialState)
{
lotSerialState
.Intercept.IsStateActive.ByConjoin(
basis => basis.IsEnterableLotSerial(isForIssue: false));
}
...
}
In this code, you will intercept the IsStateActive method and use the ByConjoin interception strategy.