HAZARD_PLACARD_EVALUATOR

Description: Allows interception of placard requirement evaluation

Abstract Base Class: com.navis.inventory.external.inventory.AbstractHazardPlacardEvaluator

Interface: EHazardPlacardEvaluator

Module: Inventory

Version Added: 3.8.7

Requires Code Extension Name or Name Pattern: Yes

Code Extension Name or Name Pattern: HazardPlacardEvaluator

System-Seeded Code Extensions Using this Type: None

 

Example: HAZARD_PLACARD_EVALUATOR

Code example

The following sample code implements a code extension of type HAZARD_PLACARD_EVALUATOR. Creating a code extension of this type overrides the built-in placard evaluation.

/*

* Copyright (c) 2020 Navis LLC. All Rights Reserved.

*

*/

 

package com.navis.external.inventory

 

import com.navis.argo.business.atoms.EquipClassEnum

import com.navis.argo.business.reference.EquipType

import com.navis.framework.business.Roastery

import com.navis.framework.metafields.MetafieldId

import com.navis.framework.metafields.MetafieldIdFactory

import com.navis.framework.persistence.HibernateApi

import com.navis.inventory.InvEntity

import com.navis.inventory.InventoryBizMetafield

import com.navis.inventory.business.imdg.HazardItem

import com.navis.inventory.business.imdg.HazardousGoods

import com.navis.inventory.business.imdg.Hazards

import com.navis.inventory.business.imdg.ImdgClass

import com.navis.inventory.business.imdg.Placard

import com.navis.inventory.business.imdg.Segregation

import com.navis.inventory.business.imdg.SubsidiaryRisk

import com.navis.inventory.business.units.GoodsBase

import com.navis.inventory.business.units.Unit

import com.navis.inventory.external.inventory.AbstractHazardPlacardEvaluator

import com.navis.orders.OrdersEntity

import com.navis.orders.business.eqorders.Booking

import com.navis.orders.business.eqorders.EquipmentOrderItem

import com.navis.road.RoadEntity

import com.navis.road.business.model.TruckTransaction

import com.navis.vessel.VesselEntity

import com.navis.vessel.business.operation.LineDischargeList

import com.navis.vessel.business.operation.LineLoadList

import org.apache.log4j.Logger

 

public class HazardPlacardEvaluator extends AbstractHazardPlacardEvaluator {

 

    private static final String LQ_PLACARD_ID = "LQ";

    private static final String MP_PLACARD_ID = "MP";

 

    /**

     * Override of teh built-in placard evaluation for a single hazard item

     * For limited quantity LQ placard is required except for UNNbrs 3166, 3171 and IMDG 1.4S when LQ is optional

     * For non-LQ IMDG1.4S specific placard is optional, otherwise required, also subsidiary risk placards are added

     * For marine pollutants MP placard is required except for UNNbrs 3166, 3171 and IMDG 1.4S when MP is optional

     * @param inHzrdi the HazardItem

     * @return Map of placards with required or optional flag

     */

    @Override

    public Map<Placard, Boolean> getPlacards(final HazardItem inHzrdi) {

 

        final Map<Placard, Boolean> placardMap = new HashMap<>();

 

        final ImdgClass imdg = inHzrdi.getHzrdiImdgClass();

        final boolean isImdg14S = ImdgClass.IMDG_14.equals(imdg.getBaseClass()) &&

                Segregation.TRAIT_Explosives_1s.equals(imdg.getCompatibilityGroupTrait());

 

        final Set<Placard> placards = new LinkedHashSet<>();

        final Set<Placard> unLabels = new LinkedHashSet<>();

 

        if (inHzrdi.getHzrdiLtdQty()) {

 

            // check for limited quantity first

            // if marked as LQ then only the LQ placard should be present

            final String placardId = LQ_PLACARD_ID;

            final Placard placard = Placard.findPlacard(placardId);

            if (placard != null) {

                boolean isOptional;

                switch (true) {

                    case "3166".equals(inHzrdi.getHzrdiUNnum()):

                    case "3171".equals(inHzrdi.getHzrdiUNnum()):

                    case isImdg14S:

                        isOptional = true;

                    default:

                        isOptional = false;

                }

                placardMap.put(placard, isOptional);

            }

            else {

                LOGGER.error("Missing placard definition for " + placardId);

            }

 

        }

        else {

 

            HazardousGoods hzgoods = HazardousGoods.findHazardousGoods(inHzrdi.getHzrdiUNnum());

            if (hzgoods != null) {

                boolean isHazardPlacardOptional;

                if (isImdg14S) {

                    isHazardPlacardOptional = true;

                }

                else {

                    isHazardPlacardOptional = false;

                }

                placardMap.put(hzgoods.getHzgoodsPlacard(), isHazardPlacardOptional);

 

                for (SubsidiaryRisk risk : SubsidiaryRisk.findSubsidiaryRisk(hzgoods.getHzgoodsGkey())) {

                    placardMap.put(risk.getSubriskPlacard(), risk.getSubriskIsPlacardOptional());

                }

 

            }

            else {

                // in the case of no hazardous goods record

                // retain placards that we've already set against the hazard itrem

                placardMap.putAll(inHzrdi.internalGetPlacards());

            }

 

            if (inHzrdi.getHzrdiMarinePollutants()) {

                final String placardId = MP_PLACARD_ID;

                final Placard placard = Placard.findPlacard(placardId);

                if (placard != null) {

                    boolean isOptional;

                    switch (true) {

                        case "3166".equals(inHzrdi.getHzrdiUNnum()):

                        case "3171".equals(inHzrdi.getHzrdiUNnum()):

                        case isImdg14S:

                            isOptional = true;

                        default:

                            isOptional = false;

                    }

                    placardMap.put(placard, isOptional);

                }

                else {

                    LOGGER.error("Missing placard definition for " + placardId);

                }

 

            }

 

        }

 

        return placardMap;

    }

 

    /**

     * Indicates if a placard is UN Nbr label

     * @param inPlacard the placard to check

     * @return true if the placard is a UN NBr label

     */

    private static boolean isUNNbrLabel(final Placard inPlacard) {

        // return true if the placard text is exactly 4 digits

        return inPlacard.getPlacardText().matches("^\\d{4}\$");

    }

 

    /**

     * Indicates if a placard is Limited Quantity

     * @param inPlacard the placard to check

     * @return true if the placard is a Limited Quantity Placard (LQ)

     */

    private static boolean isLQ(final Placard inPlacard) {

        return inPlacard.getPlacardText().equals(LQ_PLACARD_ID);

    }

 

    /**

     * Override of teh built-in placard evaluation for the set of hazard items

     * Applies weight rules for placards

     * Ensures LQ not present if specific placards are required

     * Makes sure only 1 UNNbr placard is present

     * @param inHazards the Hazards record

     * @return Map of placards with required or optional flag

     */

    @Override

    public Map<Placard, Boolean> getPlacards(final Hazards inHazards) {

        final Map<Placard, Boolean> placards = new LinkedHashMap<>();

        final Set<Placard> unLabelsOptional = new LinkedHashSet<>();

        final Set<Placard> unLabelsRequired = new LinkedHashSet<>();

 

        Double hzgoodsWt = null;

        final Long entityGkey = inHazards.getHzrdOwnerEntityGkey();

        if (entityGkey != null) {

            switch (inHazards.getHzrdOwnerEntityName()) {

                case InvEntity.GOODS_BASE:

                    final GoodsBase gds = (GoodsBase)Roastery.getHibernateApi().get(GoodsBase.class, entityGkey);

                    if (gds != null) {

                        final Unit unit = gds.getGdsUnit();

                        hzgoodsWt = (Double)unit.getFieldValue(InventoryBizMetafield.UNIT_CARGO_WEIGHT);

                    }

                    break;

                case RoadEntity.TRUCK_TRANSACTION:

                    final TruckTransaction tran = (TruckTransaction)Roastery.getHibernateApi().get(TruckTransaction.class, entityGkey);

                    if (tran != null) {

                        Double tareWt = tran.getTranCtrTareWeight();

                        Double grossWt = tran.getTranCtrGrossWeight();

                        if (grossWt != null && tareWt != null) {

                            hzgoodsWt = grossWt - tareWt;

                        }

                    }

                    break;

                case OrdersEntity.EQUIPMENT_ORDER_ITEM:

                case OrdersEntity.BOOKING:

                case OrdersEntity.RAIL_ORDER:

                case VesselEntity.LINE_LOAD_LIST:

                case VesselEntity.LINE_DISCHARGE_LIST:

                    // no weight is available for these entity types

                    break;

                default:

                    LOGGER.error("Hazard Placard Calculation: Weight field not defined for entity [" + inHazards.getHzrdOwnerEntityName() + "]");

                    break;

            }

        }

 

        boolean hasRequiredNonLQPlacard = false;

        boolean hasRequiredLQPlacard = false;

 

        // Add all the default placards except UNNbrs

        // Save the UN Nbrs for later evaluation

        for (Map.Entry<Placard, Boolean> placardEntry : inHazards.internalGetPlacards()) {

            final Placard placard = placardEntry.getKey();

            final Boolean isOptional = placardEntry.getValue();

 

            if (hzgoodsWt == null || hzgoodsWt >= placard.getPlacardMinWtKg()) {

                if (isUNNbrLabel(placard)) {

                    if (isOptional) {

                        unLabelsOptional.add(placard);

                    }

                    else {

                        unLabelsRequired.add(placard);

                    }

                }

                else {

                    placards.put(placard, isOptional);

                    if (!isOptional) {

                        if (isLQ(placard)) {

                            hasRequiredLQPlacard = true;

                        }

                        else {

                            hasRequiredNonLQPlacard = true;

                        }

                    }

                }

            }

 

        }

 

        // Only 1 UN Nbr is allowed otherwise none should be shown

        if (unLabelsRequired.size() == 1) {

            // 1 un nbr is required - add it

            placards.put(unLabelsRequired.iterator().next(), false);

            hasRequiredNonLQPlacard = true;

        }

        else if (unLabelsRequired.size() == 0 && unLabelsOptional.size() == 1) {

            // None required - see if there is only 1 optional

            placards.put(unLabelsOptional.iterator().next(), true);

        }

 

        // checks for LQ placards co-exisitng with others

        if (hasRequiredNonLQPlacard) {

            // make sure we have no LQ placards if any others are required

            removeLQPlacards(placards);

        }

        else if (hasRequiredLQPlacard) {

            // if others are not required but LQ is make sure there's only LQ

            removeNonLQPlacards(placards);

        }

 

        return placards;

    }

 

    private void removeLQPlacards(final Map<Placard, Boolean> inPlacards) {

        for (final Iterator<Map.Entry<Placard, Boolean>> itr = inPlacards.entrySet().iterator(); itr.hasNext();) {

            final Placard placard = itr.next().getKey();

            if (isLQ(placard)) {

                itr.remove();

            }

        }

    }

 

    private void removeNonLQPlacards(final Map<Placard, Boolean> inPlacards) {

        for (final Iterator<Map.Entry<Placard, Boolean>> itr = inPlacards.entrySet().iterator(); itr.hasNext();) {

            final Placard placard = itr.next().getKey();

            if (!isLQ(placard)) {

                itr.remove();

            }

        }

    }

 

    private static final Logger LOGGER = Logger.getLogger(HazardPlacardEvaluator.class.getName());

}