CostingUtils.java

package com.tradecloud.domain.costing.utils;

import com.tradecloud.domain.CommissionInformation;
import com.tradecloud.domain.costing.CostGroup;
import com.tradecloud.domain.costing.CostLine;
import com.tradecloud.domain.costing.CostLinePayerType;
import com.tradecloud.domain.costing.CostLineTemplate;
import com.tradecloud.domain.costing.clean.*;
import com.tradecloud.domain.document.invoice.ActualLineItem;
import com.tradecloud.domain.document.invoice.CalculatedCostingCell;
import com.tradecloud.domain.document.invoice.CostLineCostingCell;
import com.tradecloud.domain.document.invoice.CostingCell;
import com.tradecloud.domain.exchangerate.DefaultRateOfExchanges;
import com.tradecloud.domain.exchangerate.RateOfExchanges;
import com.tradecloud.domain.item.LineItem;
import com.tradecloud.domain.model.ordermanagement.Consignment;
import com.tradecloud.domain.model.ordermanagement.Order;
import com.tradecloud.domain.shipment.Shipment;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.*;

@Component("costingUtils")
public class CostingUtils {

    public static Logger log = Logger.getLogger(CostingUtils.class);

    /**
     * Add a specified amount to the transaction and costing currency totals.
     *
     * @param costLineSummary                The cost line summary to add amount to totals
     * @param transactionCurrencyAmountToAdd The transaction currency amount to add
     */
    public static void addToTotals(CostLineSummary costLineSummary, BigDecimal transactionCurrencyAmountToAdd) {
        // Add the sub transactionCurrencyTotal
        costLineSummary.setTransactionCurrencyTotal(costLineSummary.getTransactionCurrencyTotal().add(transactionCurrencyAmountToAdd));

        // Shouldn't round up on individual LineItem, should only round the costingCurrencyTotal CostLine result
        BigDecimal costingCurrencyAmountToAdd;
        if (costLineSummary.getForwardRate() != null) {
            costingCurrencyAmountToAdd = transactionCurrencyAmountToAdd.multiply(costLineSummary.getForwardRate());
        } else {
            costingCurrencyAmountToAdd = transactionCurrencyAmountToAdd;
        }

        // Add the sub costingCurrencyTotal
        costLineSummary.setCostingCurrencyTotal(costLineSummary.getCostingCurrencyTotal().add(costingCurrencyAmountToAdd));

//        log.debug("New costline transaction currency total=" + costLineSummary.getTransactionCurrencyTotal() + ". New cost line costing currency "
//                + "total= " + costLineSummary.getCostingCurrencyTotal());
    }

    /**
     * Creates a map of direct costs totals for each cost group.
     *
     * @param costingSummary
     * @return
     */
    public static Map<CostGroup, BigDecimal> createDirectCostGroupTotalsMap(CostingSummary costingSummary) {
        return createCostGroupTotalsMap(costingSummary, CostLinePayerType.EXPORTER);
    }

    /**
     * Creates a map of indirect costs totals for each cost group.
     *
     * @param costingSummary
     * @return
     */
    public static Map<CostGroup, BigDecimal> createIndirectCostGroupTotalsMap(CostingSummary costingSummary) {
        return createCostGroupTotalsMap(costingSummary, CostLinePayerType.IMPORTER);
    }

    /**
     * Creates a map of totals of direct or indirect costs for each cost group.
     *
     * @param costingSummary
     * @param costLinePayerType Either Importer or Exporter
     * @return
     */
    public static Map<CostGroup, BigDecimal> createCostGroupTotalsMap(CostingSummary<?, ?> costingSummary, CostLinePayerType costLinePayerType) {
        Map<CostGroup, BigDecimal> costGroupTotalsMap = new EnumMap<>(CostGroup.class);

        if (costingSummary.getCostLineSummaries() != null) {
            for (CostLineSummary costLineSummary : costingSummary.getCostLineSummaries()) {
                if (costLineSummary.getCostLine().isBaseSupply()) {
                    continue;
                }
                CostLine costLine = costLineSummary.getCostLine();
                // if a costLinePayerType was not passed then build a map of direct and indirect cost lines
                // if a costLinePayerType was passed only worry about cost lines that match the provided CostLinePayerType IMPORTER/EXPORTER param
                if (costLinePayerType == null || costLinePayerType.equals(costLine.getCostLinePayerType())) {
                    CostGroup costGroup = costLineSummary.getCostLine().getCostLineTemplate().getCostGroup();

                    // cost group totals map
                    if (costGroupTotalsMap.containsKey(costGroup)) {
                        BigDecimal currentCostGroupTotal = costGroupTotalsMap.get(costGroup);
                        currentCostGroupTotal = currentCostGroupTotal.add(costLineSummary.getCostingCurrencyTotal());
                        // replace the cost group total in the map
                        costGroupTotalsMap.put(costGroup, currentCostGroupTotal);
                    } else {
                        BigDecimal currentCostGroupTotal = costLineSummary.getCostingCurrencyTotal();
                        costGroupTotalsMap.put(costGroup, currentCostGroupTotal);
                    }
                }
            }
        }
        return costGroupTotalsMap;
    }

    /**
     * Creates a map of direct costs from cost group to their cost lines.
     *
     * @param costingSummary
     * @return
     */
    public static Map<CostGroup, List<CostLineSummary>> createDirectCostGroupCostLineSummariesMap(CostingSummary costingSummary) {
        return createCostGroupCostLineSummariesMap(costingSummary, CostLinePayerType.EXPORTER);
    }

    /**
     * Creates a map of indirect costs from cost group to their cost lines.
     *
     * @param costingSummary
     * @return
     */
    public static Map<CostGroup, List<CostLineSummary>> createIndirectCostGroupCostLineSummariesMap(CostingSummary costingSummary) {
        return createCostGroupCostLineSummariesMap(costingSummary, CostLinePayerType.IMPORTER);
    }

    /**
     * Creates a map of direct costs from cost group to their cost lines using the costLineCosting.
     *
     * @param costLineCosting
     * @return
     */
    public static Map<CostGroup, List<CostLineSummary>> createDirectCostGroupCostLineSummariesMap(CostLineCosting costLineCosting) {
        return createCostGroupCostLineSummariesMap(costLineCosting, CostLinePayerType.EXPORTER);
    }

    /**
     * Creates a map of indirect costs from cost group to their cost lines using the costLineCosting.
     *
     * @param costLineCosting
     * @return
     */
    public static Map<CostGroup, List<CostLineSummary>> createIndirectCostGroupCostLineSummariesMap(CostLineCosting costLineCosting) {
        return createCostGroupCostLineSummariesMap(costLineCosting, CostLinePayerType.IMPORTER);
    }

    /**
     * Creates a map of direct or indirect costs from cost group to their cost lines.
     *
     * @param costingSummary
     * @param costLinePayerType Either Importer or Exporter
     * @return
     */
    private static Map<CostGroup, List<CostLineSummary>> createCostGroupCostLineSummariesMap(CostingSummary<?, ?> costingSummary,
                                                                                             CostLinePayerType costLinePayerType) {
        Map<CostGroup, List<CostLineSummary>> costLinesMap = new EnumMap<>(CostGroup.class);

        if (costingSummary.getCostLineSummaries() != null) {
            for (CostLineSummary costLineSummary : costingSummary.getCostLineSummaries()) {

                CostLine costLine = costLineSummary.getCostLine();
                // only worry about cost lines that match the provided CostLinePayerType IMPORTER/EXPORTER param
                if (costLinePayerType != null && costLinePayerType.equals(costLine.getCostLinePayerType()) && !costLine.isBaseSupply()) {
                    CostGroup costGroup = costLine.getCostLineTemplate().getCostGroup();

                    // cost lines map
                    if (costLinesMap.containsKey(costGroup)) {
                        List<CostLineSummary> list = costLinesMap.get(costGroup);
                        if (!list.contains(costLineSummary)) {
                            list.add(costLineSummary);
                        }
                    } else {
                        List<CostLineSummary> newList = new ArrayList<>();
                        newList.add(costLineSummary);
                        costLinesMap.put(costGroup, newList);
                    }
                    Collections.sort(costLinesMap.get(costGroup), new CostLineSummaryDisplayComparator());
                }
            }
        }
        return costLinesMap;
    }

    /**
     * Creates a map of direct or indirect costs from cost group to their cost lines using the cost line costing.
     *
     * @param costLineCosting
     * @param costLinePayerType Either Importer or Exporter
     * @return
     */
    private static Map<CostGroup, List<CostLineSummary>> createCostGroupCostLineSummariesMap(CostLineCosting costLineCosting,
                                                                                             CostLinePayerType costLinePayerType) {
        Map<CostGroup, List<CostLineSummary>> costLinesMap = new EnumMap<>(CostGroup.class);

        if (costLineCosting.getCostLineCostingCells() != null) {
            for (CostLineCostingCell costLineCostingCell : costLineCosting.getCostLineCostingCells()) {
                CostLineSummary costLineSummary = new CostLineSummary();
                costLineSummary.setCostLine(costLineCostingCell.getCostLine());
                costLineSummary.setForwardRate(costLineCostingCell.getForwardRate());
                costLineSummary.setTransactionCurrency(costLineCostingCell.getTransactionCurrency());
                costLineSummary.setCostPercentage(costLineCostingCell.getPercentage());
                if (costLineCostingCell.getTransactionAmount() != null && costLineCostingCell.getForwardRate() != null) {
                    costLineSummary.setCostingCurrencyTotal(costLineCostingCell.getTransactionAmount()
                            .multiply(costLineCostingCell.getForwardRate()));
                }
                costLineSummary.setTransactionCurrencyTotal(costLineCostingCell.getTransactionAmount());

                CostLine costLine = costLineCostingCell.getCostLine();
                // only worry about cost lines that match the provided CostLinePayerType IMPORTER/EXPORTER param
                if (costLinePayerType != null && costLinePayerType.equals(costLine.getCostLinePayerType()) && !costLine.isBaseSupply()) {
                    CostGroup costGroup = costLine.getCostLineTemplate().getCostGroup();

                    // cost lines map
                    if (costLinesMap.containsKey(costGroup)) {
                        List<CostLineSummary> list = costLinesMap.get(costGroup);
                        if (!list.contains(costLineSummary)) {
                            list.add(costLineSummary);
                        }
                    } else {
                        List<CostLineSummary> newList = new ArrayList<>();
                        newList.add(costLineSummary);
                        costLinesMap.put(costGroup, newList);
                    }
                    Collections.sort(costLinesMap.get(costGroup), new CostLineSummaryDisplayComparator());
                }
            }
        }
        return costLinesMap;
    }

    /**
     * Maps the costlines according to their cost group.
     *
     * @param costLines
     * @return
     */
    public static Map<CostGroup, List<CostLine>> createCostGroupCostLineMap(Collection<CostLine> costLines) {
        Map<CostGroup, List<CostLine>> map = new EnumMap<>(CostGroup.class);
        // TODO. Inefficient. loops through the cost lines many times
        for (CostGroup costGroup : CostGroup.values()) {
            for (CostLine costLine : costLines) {
                if (costGroup == costLine.getCostLineTemplate().getCostGroup()) {
                    if (!map.containsKey(costGroup)) {
                        List<CostLine> list = new ArrayList<>();
                        list.add(costLine);
                        map.put(costGroup, list);
                    }
                    if (map.containsKey(costGroup) && !map.get(costGroup).contains(costLine)) {
                        map.get(costGroup).add(costLine);
                    }
                    //log.debug(costGroup + " -> " + costLine.getCostLineTemplate());
                }
            }
        }
        return map;
    }

    public static Map<CostGroup, List<String>> createCostGroupCostLineNameMap(Collection<CostLine> costLines) {
        Map<CostGroup, List<String>> map = new EnumMap<>(CostGroup.class);
        for (CostLine costLine : costLines) {
            CostGroup costGroup = costLine.getCostLineTemplate().getCostGroup();
            String name = costLine.getCostLineTemplate().getName();
            if (!map.containsKey(costGroup)) {
                List<String> list = new ArrayList<>();
                list.add(name);
                map.put(costGroup, list);
            }
            if (map.containsKey(costGroup) && !map.get(costGroup).contains(name)) {
                map.get(costGroup).add(name);
            }
            //log.debug(costGroup + " -> " + costLine.getCostLineTemplate());
        }
        return map;
    }

    public static Costed findRootParent(Costed costed) {
        if (costed.getParent() == null) {
            return costed;
        }

        return findRootParent(costed.getParent());
    }

    /**
     * Filters out costline not of the payer type.
     *
     * @param costLines
     * @param costLinePayerType
     * @return
     */
    public static Collection<CostLine> filterCostLinePayerType(Collection<CostLine> costLines, CostLinePayerType costLinePayerType) {
        Collection<CostLine> filteredCostLines = new HashSet<>();
        for (CostLine costLine : costLines) {
            if (costLine.getCostLinePayerType() == costLinePayerType) {
                filteredCostLines.add(costLine);
            }
        }
        return filteredCostLines;
    }

    public static List<CostLine> filterRemoveCostGroup(Collection<CostLine> costLines, CostGroup costGroup) {
        List<CostLine> withoutCostGroup = new ArrayList<>();

        for (CostLine costLine : costLines) {
            if (!costLine.getCostLineTemplate().getCostGroup().equals(costGroup)) withoutCostGroup.add(costLine);
        }

        return withoutCostGroup;
    }

    /**
     * Query method to return flag to indicate if this cost line is a discount cost line, this means the value will be displayed as a negative one on
     * the Estimate Cost Summary page e.g. Explicitly only the EXWorks : Discount cost line is a discount at time of implementation - request to not
     * be configurable. {@link https://jira.devstream.net/browse/BTM-863} Costing: Discount should be a negative value} Hard-coded check based on the
     * cost line name as requested {@link https://jira.devstream.net/browse/BTM-863?focusedCommentId=58751}
     *
     * @param costLine
     * @return true if cost line is EXWorks : Discount cost line return true. Otherwise, false.
     */
    public static boolean isDiscountCost(CostLine costLine) {
//        return CostLineNames.EXWORKS_DISCOUNT.equals(costLine.getCostLineTemplate().getCode());
        return costLine.getCostLineTemplate().isDiscount();
    }

    /**
     * Create CostLineCostingCell for each of the supplied cost lines.
     *
     * @param costLines
     * @param spotRate
     * @param forwardRate
     * @return
     */
    // Use the RateOfExchanges version
    @Deprecated
    public static List<CostLineCostingCell> createCostLineCostingCells(Collection<CostLine> costLines, BigDecimal spotRate, BigDecimal forwardRate) {
        List<CostLineCostingCell> costLineCostingCells = new ArrayList<>();
        for (CostLine costLine : costLines) {
            CostLineCostingCell costLineCostingCell = createCostLineCostingCell(spotRate, forwardRate, costLine);
            costLineCostingCells.add(costLineCostingCell);
        }
        return costLineCostingCells;
    }

    // Use the RateOfExchanges version
    @Deprecated
    public static CostLineCostingCell createCostLineCostingCell(BigDecimal spotRate, BigDecimal forwardRate, CostLine costLine) {
        DefaultRateOfExchanges rateOfExchanges = new DefaultRateOfExchanges(spotRate, forwardRate);
        return createCostLineCostingCell(costLine, rateOfExchanges);
    }

    public static List<CostLineCostingCell> createCostLineCostingCells(Collection<CostLine> costLines, RateOfExchanges rateOfExchanges,
                                                                       boolean primaryCosting) {
        List<CostLineCostingCell> costLineCostingCells = new ArrayList<>();
        for (CostLine costLine : costLines) {
            CostLineCostingCell costLineCostingCell = createCostLineCostingCell(costLine, rateOfExchanges);
            costLineCostingCell.setPrimaryCosting(primaryCosting);
            costLineCostingCells.add(costLineCostingCell);
        }
        return costLineCostingCells;
    }

    public static CostLineCostingCell createCostLineCostingCell(CostLine costLine, RateOfExchanges rateOfExchanges) {
        CostLineCostingCell costLineCostingCell = new CostLineCostingCell();
        costLineCostingCell.setCostLine(costLine);
        costLineCostingCell.setForwardRate(rateOfExchanges.getForwardRate());
        costLineCostingCell.setSpotRate(rateOfExchanges.getSpotRate());
        costLineCostingCell.setTransactionCurrency(costLine.getTransactionCurrency());
        costLineCostingCell.setDutiable(costLine.isDutiable());
        costLineCostingCell.setVatType(costLine.getVatType());
        return costLineCostingCell;
    }

    /**
     * Delete the related costLineCostingCells for the supplied cost lines.
     *
     * @param costLineCostingCells
     * @param costLines
     */
    public static void deleteCostLineCostingCells(Collection<CostLineCostingCell> costLineCostingCells, Collection<CostLine> costLines) {
        Collection<CostLineCostingCell> toDelete = new ArrayList<>();
        for (CostLineCostingCell costLineCostingCell : costLineCostingCells) {
            if (costLines.contains(costLineCostingCell.getCostLine())) {
                toDelete.add(costLineCostingCell);
            }
        }
        costLineCostingCells.removeAll(toDelete);
    }

    public static List<CalculatedCostingCell> createCalculatedAmounts(Collection<CostLine> costLines) {
        List<CalculatedCostingCell> calculatedAmounts = new ArrayList<>();
        for (CostLine costLine : costLines) {
            CalculatedCostingCell calculatedAmount = new CalculatedCostingCell();
            calculatedAmount.setCostLine(costLine);
            calculatedAmounts.add(calculatedAmount);
        }
        return calculatedAmounts;
    }

    public static List<CostLine> addElcCostlines(CostGroup costGroup1) {
        List<CostLine> elcCostlines = new ArrayList<>();
        String name = "name";
        String code = "code";
        elcCostlines.add(getCostLine("ELC Unit cost", costGroup1.name() + "." + ElcGlcVarianceType.ELCUnitcost.name()));
        elcCostlines.add(getCostLine("ELC CLC Variance", costGroup1.name() + "." + ElcGlcVarianceType.ELCGLCVariance.name()));
        elcCostlines.add(getCostLine("ELC Item Cost", costGroup1.name() + "." + ElcGlcVarianceType.ELCItemCost.name()));
        elcCostlines.add(getCostLine("ELC CLC Item Variance", costGroup1.name() + "." + ElcGlcVarianceType.ELCGLCItemVariance.name()));
        return elcCostlines;
    }

    private static CostLine getCostLine(String name, String code) {
        CostLineTemplate template = new CostLineTemplate();
        template.setCode(code);
        template.setName(name);
        return new CostLine(template);
    }

    public BigDecimal getCostingCurrencyTotalCost(List<CostLineSummary> costLineSummaries, String costTypeCode) {
        CostLineSummary costLineSummary = getCostLineSummary(costLineSummaries, costTypeCode);

        if (costLineSummary != null) {
            return costLineSummary.getCostingCurrencyTotal();
        }

        log.error("No cost line costingCurrencyTotal found with costTypeCode:" + costTypeCode);
        return null;
    }

    public CostLineSummary getCostLineSummary(List<CostLineSummary> costLineSummaries, String costTypeCode) {
        for (CostLineSummary costLineSummary : costLineSummaries) {
            if (costLineSummary.getCostLine().getCostLineTemplate().getCode().equals(costTypeCode)) {
                return costLineSummary;
            }
        }
        log.error("No cost line costingCurrencyTotal found with costTypeCode:" + costTypeCode);
        return null;
    }

    /**
     * Want to discourage use of this, hence its private scope - {@link CostingSummary#lookupCosting(java.lang.String)} instead.
     *
     * @param costingSummary
     * @param costTypeCode
     * @return
     * @deprecated
     */
    private CostLineSummary getCostLineSummary(CostingSummary costingSummary, String costTypeCode) {
        return costingSummary.lookupCosting(costTypeCode);
    }

    public static BigDecimal getCostLineAmount(String costLineCode, Costed costed1) {
        BigDecimal money = getCachedAmount(costed1, costLineCode);

        if (money.doubleValue() == 0.0) {
            money = getCachedCalculatedAmount(costed1, costLineCode);
        }

        if (money.doubleValue() == 0.0) {
            money = getCalculatedAmount(costed1, costLineCode);
        }

        if (money == null || money.doubleValue() == 0.0) {
            money = getCostLineCostingAmount(costed1, costLineCode);
        }

        return money;
    }

    private static BigDecimal getCostLineCostingAmount(Costed costed, String costLineCode) {
        final CostingCell costingCell = costed.getCostLineCosting().getCostingCell(costLineCode);
        return costingCell == null ? null : costingCell.getTransactionAmount();
    }

    private static BigDecimal getCalculatedAmount(Costed costed, String costLineName) {
        final CostingCell cachedCalculatedCostingCell = costed.getCostLineCosting().getCalculatedCostingCell(costLineName);

        return cachedCalculatedCostingCell == null ? null : cachedCalculatedCostingCell.getTransactionAmount();
    }

    private static BigDecimal getCachedCalculatedAmount(Costed costed, String costLineName) {
        final CostingCell cachedCalculatedCostingCell = costed.getCostLineCosting().getCachedCalculatedCostingCell(costLineName);
        if (cachedCalculatedCostingCell != null) {
            return cachedCalculatedCostingCell.getTransactionAmount();
        } else {
            return BigDecimal.ZERO;
        }

    }

    private static BigDecimal getCachedAmount(Costed costed, String costLineName) {
        final CostingCell cachedCostingCell = costed.getCostLineCosting().getCachedCostLineCostingCell(costLineName);
        if (cachedCostingCell != null) {
            return cachedCostingCell.getTransactionAmount();
        } else {
            return BigDecimal.ZERO;
        }

    }

    public static CostLine createDutiableCostCostLine() {
        CostLineTemplate dutiableCostLine = new CostLineTemplate();
        dutiableCostLine.setCode(CostingUtils.CALCULATED_DUTIABLE_COST_CODE);
        dutiableCostLine.setName(CostingUtils.CALCULATED_DUTIABLE_COST_NAME);
        CostLine calculatedDutiableCostLine = new CostLine(dutiableCostLine);

        return calculatedDutiableCostLine;
    }

    public static BigDecimal getAgentCommissionPercentage(Costed costed, BigDecimal defaultRate, CostLineSummary costLineSummary,
                                                          CostingSummary costingSummary) {
        Costed costedLineItem = findFirstCostedLineItem(costed);
        if (costedLineItem != null) {
            LineItem lineItem = getLineItem(costedLineItem, costingSummary);
            String costLineCode = costLineSummary.getCostLine().getCostLineTemplate().getCode();
            CommissionInformation commissionInfo = lineItem.getCommissionInformation();
            switch (costLineCode) {
                case CostLineNames.AGENT_FOREIGN_COMMISSION:
                    return getNotNull(commissionInfo.getForeignCommissionPercentage(),
                            commissionInfo.getDefaultForeignCommissionPercentage(), defaultRate);
                case CostLineNames.AGENT_SOURCING_COMMISSION:
                    return getNotNull(commissionInfo.getSourcingCommissionPercentage(),
                            commissionInfo.getDefaultSourcingCommissionPercentage(), defaultRate);
                case CostLineNames.AGENT_WAREHOUSING_COMMISSION:
                    return getNotNull(commissionInfo.getWarehousingCommissionPercentage(),
                            commissionInfo.getDefaultWarehousingCommissionPercentage(), defaultRate);
                case CostLineNames.AGENT_MERCHANDISING_COMMISSION:
                    return getNotNull(commissionInfo.getMerchandisingCommissionPercentage(),
                            commissionInfo.getDefaultMerchandisingCommissionPercentage(), defaultRate);
                default:
                    break;
            }
        }
        return defaultRate;
    }

    public static LineItem getLineItem(Costed costed, CostingSummary costingSummary) {
        if (costingSummary instanceof InvoiceCostingSummary || costingSummary instanceof ActualCostSummary) {
            return getLineItemFromShipment(costingSummary.getShipment(), ((ActualLineItem) costed).getOriginalId());

        } else {
            return ((CostedLineItem) costed).getLineItem();
        }
    }

    public static LineItem getLineItemFromShipment(Shipment shipment, Long originalId) {
        for (Consignment consignment : shipment.getActiveConsignments()) {
            for (Order order : consignment.getActiveOrders()) {
                for (LineItem lineItem : order.getActiveLineItems()) {
                    if (lineItem.getId().equals(originalId)) {
                        return lineItem;
                    }
                }
            }
        }
        return null;
    }

    // Do not like this! Traverses the whole shipment for nothing.
    // But might be more efficient than the above method
    public static LineItem getLineItemFromShipment(Shipment shipment, Long consignmentId, Long lineItemId) {
        for (Consignment activeConsignment : shipment.getActiveConsignments()) {
            if (activeConsignment.getId().equals(consignmentId)) {
                for (Order activeOrder : activeConsignment.getActiveOrders()) {
                    if (activeOrder.getId().equals(activeOrder.getId())) {
                        for (LineItem lineItem : activeOrder.getActiveLineItems()) {
                            if (lineItem.getId().equals(lineItemId)) {
                                return lineItem;
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    private static BigDecimal getNotNull(BigDecimal... bigDecimals) {
        for (BigDecimal bigDecimal : bigDecimals) {
            if (bigDecimal != null) {
                return bigDecimal;
            }
        }
        return null;
    }

    public static Costed findFirstCostedLineItem(Costed<? extends Costed, ? extends Costed> costed) {
        if (costed != null) {
            if (costed instanceof CostedLineItem || costed instanceof ActualLineItem) {
                return costed;
            } else {
                for (Costed child : costed.getCostedChildren()) {
                    return findFirstCostedLineItem(child);
                }
            }
        }
        return null;
    }

    public static Costed findCostedOrderOrActualOrder(Costed costed) {
        if (costed.isItem())
            return costed.getParent();
        if (costed.isOrder())
            return costed;

        return null;
    }

    //[0]: spot, [1]: forward
    public static RateOfExchanges findSpotAndForward(Costed costed, CostLineSummary costLineSummary, boolean rateFeed) {
        String costLineCode = costLineSummary.getCostLine().getCostLineTemplate().getCode();

        RateOfExchanges spotAndForward = new DefaultRateOfExchanges(costLineSummary.getSpotRate(), costLineSummary.getForwardRate());
        Costed theOrder = findCostedOrderOrActualOrder(costed);

        if (theOrder != null) {

            CostingCell cell = theOrder.getCostLineCosting().getCachedCostLineCostingCell(costLineCode);
            if (cell != null) {
                BigDecimal spotRate = rateFeed ? cell.getRateFeedSpotRate() : cell.getSpotRate();
                BigDecimal forwardRate = rateFeed ? cell.getRateFeedForwardRate() : cell.getForwardRate();
                if (spotRate != null) {
                    spotAndForward.setSpotRate(spotRate);
                }

                if (forwardRate != null) {
                    spotAndForward.setForwardRate(forwardRate);
                }
            }
        }

        return spotAndForward;
    }

    public static Costed getCostedOrderInStructure(Costed costed, Order order) {
        return findCostedOrderFromOrderInStructure(costed, order);
    }

    public static Costed findCostedOrderFromOrderInStructure(Costed costed, Order order) {
        if (CostingStructureMatcher.match(order, costed))
            //if (order.match(costed))
            return costed;

        Costed costedOrder = lookForOrderInParents(costed, order);

        if (costedOrder == null)
            costedOrder = lookForOrderInChildren(findRootParent(costed), order);

        return costedOrder;
    }

    public static Costed findActualInActualStructureMatchingEstimate(Costed actualParent, Costed estimate) {
        if (CostingStructureMatcher.match(estimate, actualParent))
            return actualParent;

        Costed costed = lookForOrderInChildren(actualParent, estimate);

        return costed;
    }

    private static Costed lookForOrderInChildren(Costed<? extends Costed, ? extends Costed> parent,
                                                 Costed<? extends Costed, ? extends Costed> lookFor) {
        if (parent.getCostedChildren() == null || parent.getCostedChildren().isEmpty())
            return null;

        Costed found = null;

        for (Costed child : parent.getCostedChildren()) {
            if (CostingStructureMatcher.match(lookFor, child)) {
                found = child;
            } else {
                found = lookForOrderInChildren(child, lookFor);
            }

            if (found != null)
                break;
        }

        return found;
    }

    private static Costed lookForOrderInChildren(Costed<? extends Costed, ? extends Costed> parent, Order order) {
        if (parent.getCostedChildren() == null || parent.getCostedChildren().isEmpty())
            return null;

        Costed found = null;

        for (Costed child : parent.getCostedChildren()) {
            if (CostingStructureMatcher.match(order, child)) {
                //if (order.match(child)) {
                found = child;
            } else {
                found = lookForOrderInChildren(child, order);
            }

            if (found != null)
                break;
        }

        return found;
    }

    private static Costed lookForOrderInParents(Costed child, Order order) {
        if (child.getParent() == null)
            return null;

        Costed parent = child.getParent();

        if (CostingStructureMatcher.match(order, parent)) {
            //if (order.match(parent)) {
            return parent;
        }

        return lookForOrderInParents(parent, order);
    }

    public static CostedLineItem getCostedLineItem(Costed costed, LineItem lineItem) {
        if (costed instanceof CostedLineItem) {
            CostedLineItem costedLineItem = (CostedLineItem) costed;
            //if (lineItem.match(costedLineItem)) {
            if (CostingStructureMatcher.match(lineItem, costedLineItem)) {
                return costedLineItem;
            }
        } else {
            for (Object object : costed.getCostedChildren()) {
                CostedLineItem costedLineItem = getCostedLineItem((Costed) object, lineItem);
                if (costedLineItem != null) {
                    return costedLineItem;
                }
            }
        }
        return null;
    }

    public static List<LineItem> getLineItems(Consignment consignment) {
        List<LineItem> lineItems = new ArrayList<>();
        for (Order order : consignment.getOrders()) {
            lineItems.addAll(order.getLineItems());
        }
        return lineItems;
    }

    public static <T extends Object> void moveOtherCostLineToBeLast(List<T> costLines) {
        List<String> otherCostLines = new ArrayList<String>();
        otherCostLines.add(CostLineNames.AGENT_OTHER);
        otherCostLines.add(CostLineNames.EXWORKS_OTHER);
        otherCostLines.add(CostLineNames.FORWARDING_OTHER);
        otherCostLines.add(CostLineNames.FREIGHT_OTHER);
        otherCostLines.add(CostLineNames.FINANCE_OTHER);
        otherCostLines.add(CostLineNames.CLEARING_OTHER);
        otherCostLines.add(CostLineNames.CUSTOMS_OTHER);
        otherCostLines.add(CostLineNames.INTERNAL_PROVISIONS_OTHER);

        T toAdd = null;

        for (T costLine : costLines) {
            String code = "";
            if (costLine instanceof CostLine) {
                code = ((CostLine) (costLine)).getCostLineTemplate().getCode();
            } else if (costLine instanceof CostLineSummary) {
                code = ((CostLineSummary) (costLine)).getCostLine().getCostLineTemplate().getCode();
            } else {
                throw new RuntimeException("Invalid input, requires CostLine or CostLineSummary.");
            }

            if (otherCostLines.contains(code)) {
                toAdd = costLine;
                costLines.remove(costLine);
                break;
            }
        }

        if (toAdd != null) costLines.add(toAdd);
    }

    public static final String CALCULATED_DUTIABLE_COST_CODE = "CALCULATED_DUTIABLE_COST";
    public static final String CALCULATED_DUTIABLE_COST_NAME = "Dutiable Cost";
    public static final String CALCULATED_CUSTOMS_DUTY_CODE = "CALCULATED_CUSTOMS_DUTY";
    public static final String CALCULATED_CUSTOMS_DUTY_NAME = "Calculated Customs Duty";
    public static final String CALCULATED_TOTAL_DIRECT_COST_CODE = "CALCULATED_TOTAL_DIRECT_COST_CODE";
    public static final String CALCULATED_TOTAL_DIRECT_COST_NAME = "Total Direct Cost";
    public static final String CALCULATED_CUSTOMS_VAT = "CALCULATED_CUSTOMS_VAT";
}