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