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:

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);

  }

}