INV_CUSTOM_INVENTORY_CORRECTION_VALIDATOR
Description: Limited to code that determines whether a unit facility visit (UFV) may be inventoried directly to the Yard (without the Hatch Clerk) to a specific railcar.
You use the INV_CUSTOM_INVENTORY_CORRECTION_VALIDATOR extension type to allow inventorying UFVs directly from a vessel to:
The Yard, without using the Hatch Clerk
A railcar, without moving them first to yard locations using N4 Mobile Rail Inventory.
A code extension using this extension type would enable direct moves from vessels to the Yard or to railcars.
Abstract Base Class: AbstractCustomInventoryCorrectionValidator
Method: isValidInventoryToRailcar (for Rail Inventory) and isValidInventoryToYard (for Yard Inventory)
The method is expected to return a boolean value that indicates whether the specific UFV can be inventoried to a specific Yard or railcar position, regardless of its origin (vessel or yard).
Interface: ECustomInventoryCorrectionValidator
Module: Inventory
Version Added: 2.3
Requires Code Extension Name or Name Pattern: No
Code Extension Name or Name Pattern: Recommended to follow a pattern of ModuleName_Functionality (but not required). This name pattern makes it easy to identify the module to which the particular functionality belongs.
Where to Specify Code Extensions of this Type: Not needed. N4 registers code extensions of this type inside the particular module's ExtensionTypes.xml file. For example, if the extension is for the Inventory module, N4 writes the code extension to the InventoryExtensionTypes.xml.
System-Seeded Code Extensions Using this Type: None
Code example
The following sample groovy code overrides the N4 restrictions that prevent inventorying a container on a vessel to a railcar or to the Yard.
package extension.system
import com.navis.argo.business.model.LocPosition
import com.navis.inventory.business.atoms.UfvTransitStateEnum
import com.navis.inventory.business.units.UnitFacilityVisit
import com.navis.inventory.external.inventory.AbstractCustomInventoryCorrectionValidator
import com.navis.inventory.business.units.Unit
import com.navis.inventory.business.units.Routing
import com.navis.argo.business.model.CarrierVisit
import com.navis.argo.business.atoms.LocTypeEnum
import com.navis.xpscache.business.atoms.EquipBasicLengthEnum
import com.navis.argo.ContextHelper
import com.navis.argo.business.atoms.CarrierVisitPhaseEnum
import com.navis.inventory.business.units.MoveInfoBean
import com.navis.argo.business.atoms.WiMoveKindEnum
import com.navis.argo.business.api.ArgoUtils
import com.navis.inventory.InventoryPropertyKeys
import org.jetbrains.annotations.Nullable
import com.navis.spatial.business.model.AbstractBin
import com.navis.inventory.business.api.UnitManager
import com.navis.framework.business.Roastery
/**
* This is groovy is invoked from the N4 Mobile Yard/Rail Inventory Application. If implemented this groovy is responsible for determining
* if a UFV can be inventoried onto a Yard/Railcar or not. Basic Product implementation only allows for UFV in Rail position to be inventoried
* onto a railcar and Yard position to be inventoried on Yard. This is useful in the instance that a container was advised to arrive on a particular
* carrier but bound for the same inventory position.
*
* The N4 Mobile Rail Inventory only invokes isValidInventoryToRailcar irrespective of whether the UFV is in Yard or not where Yard Inventory
* invoked in case of Yard only. So, if this groovy is overridden it should handle the cases appropriately. The groovy overrides any product
* validation and the result provided by the groovy implementation trumps. This may change in future versions and we may have two separate call outs.
*/
public class CustomInventoryCorrectionValidator extends AbstractCustomInventoryCorrectionValidator {
public boolean isValidInventoryToRailcar(UnitFacilityVisit inUfv, LocPosition inLocPosition) {
if (inUfv == null) {
log("Received Null UFV. Will return false")
return false;
}
log("Entered CustomRailInventoryCorrectionValidator.isValidInventoryToRailcar for UFV " + inUfv.getUfvUnit().getUnitId())
if (inLocPosition == null) {
log("Received Null inPosition. Will return false")
return false;
}
boolean result = false;
LocPosition currentPos = inUfv.getUfvLastKnownPosition()
if (LocTypeEnum.YARD.equals(currentPos.getPosLocType())) {
log("UFV is in yard.")
return super.isValidInventoryToRailcar(inUfv, inLocPosition);
}
else if (LocTypeEnum.VESSEL.equals(currentPos.getPosLocType())) {
log(("UFV is on vessel"))
result = invokeValidationsForVessel(inUfv, inLocPosition)
}
log("Leaving CustomRailInventoryCorrectionValidator.isValidInventoryToRailcar for UFV " + inUfv.getUfvUnit().getUnitId())
return result;
}
// Railcar Inventory update from vessel position is valid only if the UFV T-State is EC-In to indicate that the ufv was
//discharged from inbound carrier and the UFV's OB Carrier is a train.
boolean invokeValidationsForVessel(UnitFacilityVisit inUfv, LocPosition inLocPosition) {
boolean result = false;
log("Entered CustomRailInventoryCorrectionValidator.invokeValidationsForVessel for UFV " + inUfv.getUfvUnit().getUnitId())
if (UfvTransitStateEnum.S30_ECIN.equals(inUfv.getUfvTransitState())) {
log(" Ufv: " + inUfv.getUfvUnit().getUnitId() + "T-State is: " + inUfv.getUfvTransitState())
Unit unit = inUfv.getUfvUnit();
Routing rtg = unit.getUnitRouting();
CarrierVisit cv = rtg.getRtgDeclaredCv();
if (LocTypeEnum.RAILCAR.equals(cv.getCvCarrierMode()) || LocTypeEnum.TRAIN.equals(cv.getCvCarrierMode())) {
log("Unit departs on: " + cv.getCvCarrierMode())
result = true;
} else {
log("Unit is not declared to depart on RAILCAR or TRAIN")
}
} else {
log("UFV T-State is not EC-In. Cannot inventory to rail car. Will return false")
}
log("Leaving CustomRailInventoryCorrectionValidator.invokeValidationsForVessel for UFV " + inUfv.getUfvUnit().getUnitId())
return result;
}
//Unless extended we want to have the default behavior.
public boolean isValidInventoryToYard(UnitFacilityVisit inUfv, LocPosition inLocPosition) {
// replace the default return to this method if direct Vessel discharge is desired.
//EquipBasicLengthEnum eqLength = inUfv.getUfvUnit().getUnitPrimaryUe().getUeEquipment().getEqEquipType().getEqtypBasicLength();
//return validateUfvForPositionCorrection(inUfv, inLocPosition.posName(), eqLength);
return super.isValidInventoryToYard(inUfv, inLocPosition);
}
// This is a sample method to direct discharge from Vessel.
private boolean validateUfvForPositionCorrection(UnitFacilityVisit inUfv,
String inPos, EquipBasicLengthEnum inEqLength) {
inUfv.getUfvUnit().getUnitPrimaryUe().getUeEquipment().getEqEquipType().getEqtypBasicLength();
LocPosition newPosition;
//if eq length is 40ft then create the appropriate 40ft position name
if (EquipBasicLengthEnum.BASIC40.equals(inEqLength)) {
String binName40ft = get40FtBinName(inPos);
newPosition = LocPosition.createYardPosition(ContextHelper.getThreadYard(), binName40ft, null, inEqLength, true);
} else {
newPosition = LocPosition.createYardPosition(ContextHelper.getThreadYard(), inPos, null, inEqLength, true);
}
LocPosition currPosition = inUfv.getUfvLastKnownPosition();
LocTypeEnum currLocType = currPosition.getPosLocType();
String unitId = inUfv.getUfvUnit().getUnitId();
Boolean isPositionCorrected = false;
if (currLocType != null) {
if (LocTypeEnum.VESSEL.equals(currLocType) || LocTypeEnum.TRAIN.equals(currLocType)) {
CarrierVisit cv = inUfv.getInboundCarrierVisit();
if (cv == null) {
log("There is no inbound carrier visit associated for unit " + unitId);
}
CarrierVisitPhaseEnum cvPhaseEnum = cv.getCvVisitPhase();
if ((CarrierVisitPhaseEnum.ARRIVED.equals(cvPhaseEnum) || CarrierVisitPhaseEnum.WORKING.equals(cvPhaseEnum)) &&
!UfvTransitStateEnum.S60_LOADED.equals(inUfv.getUfvTransitState())) {
//discharge the container from Rail or Vessel
MoveInfoBean info = MoveInfoBean.createDefaultMoveInfoBean(WiMoveKindEnum.VeslDisch, ArgoUtils.timeNow());
info.setRestowReason(inUfv.getUfvHandlingReason() != null ? inUfv.getUfvHandlingReason().getKey() : null);
getMngr().dischargeUnitFromInboundVisit(inUfv, cv.getCvCvd(), info, inPos, null);
isPositionCorrected = true;
} else { //throw error message that the CTR is on a Carrier
if (UfvTransitStateEnum.S60_LOADED.equals(inUfv.getUfvTransitState())) {
cv = currPosition.resolveOutboundCarrierVisit();
if (cv == null) {
log("There is no outbound carrier visit associated for unit " + unitId);
}
cvPhaseEnum = cv.getCvVisitPhase();
}
String errStr =
new StringBuffer("Unit " + unitId + " on ").append(cvPhaseEnum.getKey().substring(2)).append(" ")
.append(currLocType.getName())
.toString();
log(errStr);
}
}
else if (LocTypeEnum.TRUCK.equals(currLocType)) {
if (UfvTransitStateEnum.S60_LOADED.equals(inUfv.getUfvTransitState())) {
String errStr = "Unit " + unitId + " loaded on TRUCK";
log(errStr);
}
inUfv.correctPosition(newPosition, false);
isPositionCorrected = true;
}
}
return isPositionCorrected;
}
/**
* Helper method to return the 40 ft bin name given the 20 ft bin name
*
*/
public static String get40FtBinName(String inA20FtBinName) {
LocPosition pos = LocPosition.createYardPosition(ContextHelper.getThreadYard(), inA20FtBinName, null, EquipBasicLengthEnum.BASIC20, true);
if (pos != null) {
AbstractBin bin = pos.getPosBin();
if (bin != null) {
String binName = bin.getAbnNameAlt();
if (binName != null && !binName.isEmpty()) {
StringBuilder binName40 = new StringBuilder(binName);
int lastDot = inA20FtBinName.lastIndexOf('.');
if (lastDot > 0) {
String tier = inA20FtBinName.substring(lastDot + 1, inA20FtBinName.length());
binName40.append(".").append(tier);
//create a new position with the 40ft bin name to check it's a valid bin name
LocPosition.createYardPosition(ContextHelper.getThreadYard(), binName40.toString(), null, EquipBasicLengthEnum.BASIC40, true);
return binName40.toString();
}
}
}
}
return null;
}
public static UnitManager getMngr() {
return (UnitManager) Roastery.getBean(UnitManager.BEAN_ID);
}
}