EstimateCostSummary.java

package com.tradecloud.domain.costing.clean;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.tradecloud.domain.base.utils.MathUtils;
import com.tradecloud.domain.common.Currency;
import com.tradecloud.domain.costing.CostGroup;
import com.tradecloud.domain.costing.CostLine;
import com.tradecloud.domain.costing.utils.CostingUtils;
import com.tradecloud.domain.document.invoice.ActualConsignment;
import com.tradecloud.domain.document.invoice.ActualLineItem;
import com.tradecloud.domain.document.invoice.UnitPricePerItem;
import com.tradecloud.domain.model.ordermanagement.Consignment;
import com.tradecloud.domain.model.ordermanagement.Order;
import com.tradecloud.domain.model.organisationalunit.OrganisationalUnit;
import com.tradecloud.domain.party.ServiceProvider;
import com.tradecloud.domain.shipment.Shipment;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.math.BigDecimal;
import java.util.*;

@Entity
@DiscriminatorValue("ESTIMATE_COST_SUMMARY")
@Table(name = "estimatecostsummary")
@Access(AccessType.FIELD)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "EstimateCostSummary")
@NamedQueries({@NamedQuery(name = "findByIdWithCostLineCostingCellsLoaded",
        query = "from EstimateCostSummary ecs left join fetch ecs.costedConsignment.costLineCosting.costLineCostingCells where ecs.id=:id")})
public class EstimateCostSummary extends CostingSummary<CostedConsignment, Consignment> {

    /**
     * UID.
     */
    private static final long serialVersionUID = 1L;

    /**
     * Original consignment used for retrieval of the EstimateCostSummary only and not for the costing.
     */
    @NotNull
    @XmlElement(name = "OriginalCostable")
    @OneToOne(optional = false)
    @JsonIgnore
    private Consignment originalCostable;

    /**
     * The estimate cost 'snapshot' of a Consignment at a point in time.
     */
    @NotNull
    @XmlElement(name = "CostedConsignment")
    @OneToOne(optional = false, cascade = CascadeType.ALL)
    @JsonIgnore
    private CostedConsignment costedConsignment;

    private String number;

    private boolean primaryCosting;

    @Embedded
    private CostSummaryAdditionalInfo costSummaryAdditionalInfo = new CostSummaryAdditionalInfo();

    private transient List<CostLine> elcCostLines = new ArrayList<>();

    @Override
    public CostedConsignment getCostable() {
        return getCostedConsignment();
    }

    @Override
    public void setCostable(CostedConsignment costable) {
        setCostedConsignment((CostedConsignment) costable);
    }

    public CostedConsignment getCostedConsignment() {
        return costedConsignment;
    }

    public void setCostedConsignment(CostedConsignment costedConsignmentParam) {
        this.costedConsignment = costedConsignmentParam;
    }

    @Override
    @JsonIgnore
    public Consignment getOriginalCostable() {
        return originalCostable;
    }

    @Override
    public void setOriginalCostable(Consignment originalCostable) {
        this.originalCostable = originalCostable;
    }

    /**
     * Returns the costedConsignment which reflects the CLC costed structure.
     *
     * @return
     */
    @Override
    public Costed<? extends Costed, ? extends Costed> getCostedStructure() {
        return costedConsignment;
    }

    public BigDecimal lookupCostingResult(String costLineCode, Order order) {
        final List<CostedOrder> costedOrdersList = getCostedConsignment().getCostedOrdersList();
        for (CostedOrder costedOrder : costedOrdersList) {
            if (CostingStructureMatcher.match(order, costedOrder)) {
                //if (order.match(costedOrder)) {
                return CostingUtils.getCostLineAmount(costLineCode, costedOrder);
            }
        }
        return null;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(originalCostable).append(costedConsignment).toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;

        if (!(obj instanceof EstimateCostSummary)) {
            return false;
        }

        EstimateCostSummary other = (EstimateCostSummary) obj;
        return new EqualsBuilder().append(originalCostable, other.originalCostable).append(costedConsignment, other.costedConsignment).isEquals();
    }

    @Override
    public Set<ActualConsignment> getActualConsignmentSet() {
        return null;
    }

    @Override
    @JsonIgnore
    public Shipment getShipment() {
        return null;
    }

    @Override
    public boolean isTotalUnitPrice(Costed costed) {
        if (costed instanceof CostedLineItem) {
            //UnitPricePerItem unitPricePerItem = ((CostedLineItem) costed).getLineItem().getOrder().getUnitPricePerItem();
            //return UnitPricePerItem.TOTAL_UNIT_PRICE.equals(unitPricePerItem);
            return UnitPricePerItem.TOTAL_UNIT_PRICE.equals(((CostedLineItem) costed).getCostedOrder().getUnitPricePerItem());
        }
        return false;
    }

    @Override
    public void clearCostApplicationBasisAmounts() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<CostLine> getElcCostlines(CostGroup costGroup) {
        if (elcCostLines.isEmpty() && !costedConsignment.getElcGlcVarianceMap().isEmpty()) {
            elcCostLines = (List<CostLine>) CostingUtils.addElcCostlines(costGroup);
        }
        return elcCostLines;
    }

    public boolean isElcCostingConsignment() {
        return !getCostedConsignment().getElcGlcVarianceMap().isEmpty();
    }

    public CostSummaryAdditionalInfo getCostSummaryAdditionalInfo() {
        if (costSummaryAdditionalInfo == null) {
            costSummaryAdditionalInfo = new CostSummaryAdditionalInfo();
        }
        return costSummaryAdditionalInfo;
    }

    public void setCostSummaryAdditionalInfo(CostSummaryAdditionalInfo costSummaryAdditionalInfo) {
        this.costSummaryAdditionalInfo = costSummaryAdditionalInfo;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public OrganisationalUnit findStdRateOrgUnit() {
        Order order = getOriginalCostable().getOrders().stream().findFirst().orElse(null);
        return order != null ? order.getOrganisationalUnit() : null;
    }

    @Override
    public BigDecimal lookupMatchingItemAmount(ActualLineItem actual, String costLineCode) {
        CostedLineItem costedLineItem = costedConsignment.getAllCostedLineItems().stream()
                .filter(c -> c.getLineItem().getId().equals(actual.getOriginalId())).findFirst()
                .orElseThrow(() -> new IllegalStateException("cannot find matching estimate costed lineitem for actual item: " +
                        "" + actual.getCode() + " order: " + actual.getActualOrder().getReference()));
        return costedLineItem.getCostLineCosting().getCostingCell(costLineCode).getTransactionAmount();
    }

    @Override
    public boolean isPrimaryCosting() {
        return primaryCosting;
    }

    @Override
    public void setPrimaryCosting(boolean primaryCosting) {
        this.primaryCosting = primaryCosting;
    }

    public ServiceProvider getFreightForwarder() {
        return getCostSummaryAdditionalInfo().getFreightForwarder();
    }

    public void setFreightForwarder(ServiceProvider freightForwarder) {
        getCostSummaryAdditionalInfo().setFreightForwarder(freightForwarder);
    }

    public BigDecimal getCostGroupExchangeRateClcELC(com.tradecloud.domain.costing.CostGroup costGroup,
                                                     Currency costingCurrency) {
        BigDecimal costGroupRate = BigDecimal.ONE;
        Map<Currency, BigDecimal> currencyExchanges = new HashMap<>();
        BigDecimal totalAmountForeign = BigDecimal.ZERO;
        BigDecimal totalAmountCosting = BigDecimal.ZERO;

        for (CostLineSummary costLineSummary : this.getCostLineSummaries()) {
            if (isValidCostLine(costLineSummary, costingCurrency, costGroup)) {
                Currency transactionCurrency = costLineSummary.getTransactionCurrency();
                currencyExchanges.putIfAbsent(transactionCurrency, costLineSummary.getForwardRate());
                totalAmountForeign = totalAmountForeign.add(costLineSummary.getTransactionCurrencyTotal());
                totalAmountCosting = totalAmountCosting.add(getCostingAmount(costLineSummary));
            }
        }

        if (currencyExchanges.size() == 1) {
            return currencyExchanges.values().iterator().next();
        } else if (currencyExchanges.size() > 1) {
            return MathUtils.divideVA(totalAmountCosting, totalAmountForeign);
        }
        return costGroupRate;
    }

    private boolean isValidCostLine(CostLineSummary costLineSummary, Currency costingCurrency, com.tradecloud.domain.costing.CostGroup costGroup) {
        return costLineSummary.getCostLine().getCostLineTemplate().getCostGroup()
                .equals(costGroup)
                && MathUtils.isNonZero(costLineSummary.getTransactionCurrencyTotal())
                && !costingCurrency.equals(costLineSummary.getTransactionCurrency());
    }

    private BigDecimal getCostingAmount(CostLineSummary costLineSummary) {

        return costLineSummary.getTransactionCurrencyTotal().multiply(costLineSummary.getForwardRate());
    }

}