Skip to main content

Hey all trying to modify the ScanMove Screen in two ways;

  1. Combine the Production Order ID and the Operation ID in a single scan.  Currently you are prompted first for the production order id then for the operation id.  So the change would be to maybe add a new scan item that parses to the two fields and then skips the two fields after populated, or anything that would combine the two.  Don’t care just need it to work
  2. Add the Scap quantity into the grid on scan move.  Its not there.  Its there on the move screen but we forgot that one for some reason.  

Both of these tasks sound very simple but access to everything in the Automated Warehouse Operation Engine it is not.  Everything seems to be in a sealed class or so far abstracted down the line that I can’t get to it. Examples are skimpy to say the least and none Add a new barcode scan item (there is one that you add a cancel button scan but thats not what I am looking for).   Injection is used to modify code and I can change the order of things and validations but Simple things like adding a field or barcode scan to an item seems not possible.  Maybe I’m just not seeing it.  wondering if anyone has any insight. 

I will suggest the below approach:

  1. 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 { }
  2. 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.
  3. 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.
  4. 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 => newe] { new newProdState() }); )
  5. 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;

// properties for Operation
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)
{
//change your logic to parse or split both so that it can be used in Apply later
return barcode;
}

protected override Validation Validate(string value)
{
//validate the Prod Order
//separate the Prod Order ID and OperationID //replace with your logic to parse the barcode to get both the values
string prodID = value.Substring(0, 8);
string operNbr = value.Substring(8, 4);
//logic to apply Prod Order ID first
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)
{
//separate the Prod Order ID and OperationID //replace with your logic to parse the barcode to get both the values
string prodID = value.Substring(0, 8);
string operNbr = value.Substring(8, 4);
//logic to apply Prod Order ID first
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;
}
//logic to apply OperationID
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)); //replace the string with necessary parsing


#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
moveMode.Intercept.CreateStates.ByAppend(basis => new ] { new ProdState() });

moveMode
// the ByAppend overriding strategy helps to simplify adding new components without even touching the existing ones
.Intercept.CreateTransitions.ByReplace(() =>
{
return Base1.StateFlow(flow => flow
.From<WMSBase.OrderTypeState>()
//.NextTo<WMSBase.ProdOrdState>()
//.NextTo<ScanMove.MoveMode.OperationState>()
.NextTo<ProdState>()
.NextTo<WMSBase.WarehouseState>()
.NextTo<WMSBase1.LocationState>()
.NextTo<WMSBase1.LotSerialState>()
.NextTo<WMSBase1.ExpireDateState>()
.NextTo<ScanMove.MoveMode.ConfirmState>()
);
});
}
return mode;
}
}

 


Reply