I will suggest the below approach:
- In order to extend any WMS graph, you need to create a ScanExtension graph. In your case it would be for ScanMove graph and is created as public class ScanMoveExt : ScanMove.ScanExtension { }
- With respect to your requirement, Move is a mode and Production Order ID and Operation scans are separate states. In order to have one scan for both Production Order ID and Operation, you can either use one of the states and interpret Apply method to combine the production order id and operation and set the corresponding values in Basis object or you can create a new state which will get the combined barcode and set those values to the Basis object.
- I would recommend you to create a new state to avoid confusion and remove ProdOrdState and OperationState from Move mode. While defining the new state (example: newProdState), you also need to override Apply method – which is used to apply the values to the corresponding fields inside Basis object. So, in this, you need to interpret the barcode and separate Operation number and Production Order ID and apply those values respectively.
- Now, after defining the new state, you need to add that new state (newProdState) to the ScanMove mode. In order to do that, you need to override DecorateScanMode first to modify just Move mode and then intercept CreateStates method to append the newly created state. (moveMode.Intercept.CreateStates.ByAppend(basis => new[] { new newProdState() }); )
- After this, you need to add a transition for that state after OrderTypeState and remove transitions for ProdOrdState and OperationState which can be done by intercepting CreateTransitions inside DecorateScanMode method and replace the transitions as mentioned in the article.
The only thing to note here is that the OperationState has some properties like IsLastOperation and has some logic related to Qty correction in the corresponding Apply method which you need to have inside your new State’s Apply method. So, I would recommend you to go through all the methods inside both ProdOrdState and OperationState states.
using static PX.Objects.CT.ContractItem;
using WMSBase = ScanProductionBase<ScanMove, ScanMove.Host, AMDocType.move>;
using WMSBase1 = WarehouseManagementSystem<ScanMove, ScanMove.Host>;
[PXProtectedAccess(typeof(ScanMove))]
public abstract class ScanMoveExt : ScanMove.ScanExtension
{
public static bool IsActive() => true;
[PXProtectedAccess(typeof(ScanMove))]
public abstract bool UseQtyCorrectection { get; }
[PXProtectedAccess(typeof(ScanMove))]
public abstract bool PromptLocationForEveryLine { get; }
[PXProtectedAccess(typeof(ScanMove))]
public abstract bool UseRemainingQty { get; }
public class ProdState : ScanMove.EntityState<string>
{
public ScanMoveExt MBasis => Basis.Get<ScanMoveExt>();
public const string Value = "PRODOPER";
public class value : BqlString.Constant<value> { public value() : base(ProdState.Value) { } }
public override string Code => Value;
protected override string StatePrompt => Msg.Prompt;
protected decimal DefaultQty => 1m;
protected bool HasDefaultedQty { get; set; }
protected bool HasDefaultedLocationID { get; set; }
protected bool IsLastOperation { get => Basis.LastOperationID != null && Basis.LastOperationID == Basis.OperationID; }
protected override string GetByBarcode(string barcode)
{
return barcode;
}
protected override Validation Validate(string value)
{
string prodID = value.Substring(0, 8);
string operNbr = value.Substring(8, 4);
if (Basis.OrderType == null && Basis.Setup.Current.UseDefaultOrderType == true)
{
Basis.OrderType = Basis.AMSetup.Current.DefaultOrderType;
}
AMProdItem prodItem = AMProdItem.PK.Find(Basis, Basis.OrderType, prodID);
if (prodItem.Function == OrderTypeFunction.Disassemble)
return Validation.Fail(Msg.ProdOrdWrongStatus, prodItem.OrderType, prodItem.ProdOrdID, ProductionOrderStatus.GetStatusDescription(prodItem.StatusID));
else if (!ProductionStatus.IsReleasedTransactionStatus(prodItem))
return Validation.Fail(Msg.UnreleasedOrder, prodItem.OrderType, prodItem.ProdOrdID);
var branch = Branch.PK.Find(Basis, prodItem.BranchID);
if (branch == null || branch.BaseCuryID != Basis.Graph.Accessinfo.BaseCuryID)
return Validation.Fail(PX.Objects.AM.Messages.BranchBaseCurrencyDifference, branch.BaseCuryID, Basis.Graph.Accessinfo.BaseCuryID);
return Validation.Ok;
}
protected override void Apply(string value)
{
string prodID = value.Substring(0, 8);
string operNbr = value.Substring(8, 4);
if (Basis.OrderType == null && Basis.Setup.Current.UseDefaultOrderType == true)
{
Basis.OrderType = Basis.AMSetup.Current.DefaultOrderType;
}
AMProdItem prodItem = AMProdItem.PK.Find(Basis, Basis.OrderType, prodID);
Basis.ProdOrdID = prodItem.ProdOrdID;
Basis.ParentLotSerialRequired = prodItem.ParentLotSerialRequired;
if (Basis.DocType == AMDocType.Move || Basis.DocType == AMDocType.Labor)
{
Basis.InventoryID = prodItem.InventoryID;
Basis.UOM = prodItem.UOM;
Basis.LaborType = AMLaborType.Direct;
Basis.LastOperationID = prodItem.LastOperationID;
}
AMProdOper prodOper = AMProdOper.UK.Find(Basis.Graph, Basis.OrderType, prodID, operNbr);
Basis.OperationID = prodOper.OperationID;
if (MBasis.UseRemainingQty && (Basis.Qty == DefaultQty || MBasis.UseQtyCorrectection))
{
Basis.Qty = prodOper?.QtyRemaining ?? DefaultQty;
HasDefaultedQty = true;
}
if (IsLastOperation != true)
{
if (prodItem != null)
{
Basis.SiteID = prodItem.SiteID;
if (!MBasis.PromptLocationForEveryLine)
{
Basis.LocationID = prodItem.LocationID;
HasDefaultedLocationID = true;
}
}
}
}
protected override void ClearState() {
Basis.ProdOrdID = null;
Basis.OperationID = null;
if (HasDefaultedQty)
{
Basis.Qty = DefaultQty;
HasDefaultedQty = false;
}
if (HasDefaultedLocationID)
{
Basis.LocationID = null;
HasDefaultedLocationID = false;
}
}
protected override void ReportSuccess(string value) => Basis.Reporter.Info(Msg.Ready, value.Substring(0, 7));
#region Messages
[PXLocalizable]
public abstract class Msg
{
public const string Prompt = "Scan the barcode.";
public const string Missing = "The {0} production order is not found.";
public const string ProdOrdWrongType = "The production order {0} is a Disassembly type.";
public const string ProdOrdWrongStatus = "The production order {0}, {1} has a status of {2}";
public const string Ready = "The {0} production order is selected.";
public const string UnreleasedOrder = "The production order {0} {1} has not been released.";
}
#endregion
}
[PXOverride]
public virtual ScanMode<ScanMove> DecorateScanMode(
ScanMode<ScanMove> original, // however while decorating components only the ScanComponent<WMS> form must be used, you can not override this method using the WMS.ScanComponent form
Func<ScanMode<ScanMove>, ScanMode<ScanMove>> base_DecorateScanMode)
{
var mode = base_DecorateScanMode(original);
if (mode is ScanMove.MoveMode moveMode)
{
moveMode.Intercept.CreateStates.ByAppend(basis => new[] { new ProdState() });
moveMode
.Intercept.CreateTransitions.ByReplace(() =>
{
return Base1.StateFlow(flow => flow
.From<WMSBase.OrderTypeState>()
.NextTo<ProdState>()
.NextTo<WMSBase.WarehouseState>()
.NextTo<WMSBase1.LocationState>()
.NextTo<WMSBase1.LotSerialState>()
.NextTo<WMSBase1.ExpireDateState>()
.NextTo<ScanMove.MoveMode.ConfirmState>()
);
});
}
return mode;
}
}