CostsInvoice.java

package com.tradecloud.domain.document.invoice;

import com.tradecloud.common.base.HibernateUtils;
import com.tradecloud.common.base.PersistenceBase;
import com.tradecloud.domain.comment.AddedCommentIncCost;
import com.tradecloud.domain.comment.CommentType;
import com.tradecloud.domain.comment.Commentable;
import com.tradecloud.domain.common.Currency;
import com.tradecloud.domain.common.Incoterm;
import com.tradecloud.domain.configuration.SPICostlineCurrency;
import com.tradecloud.domain.container.Container;
import com.tradecloud.domain.costing.*;
import com.tradecloud.domain.costing.clean.*;
import com.tradecloud.domain.document.Document;
import com.tradecloud.domain.document.PaymentState;
import com.tradecloud.domain.export.ExportCosting;
import com.tradecloud.domain.export.ExportParty;
import com.tradecloud.domain.item.ItemType;
import com.tradecloud.domain.model.ordermanagement.Consignment;
import com.tradecloud.domain.model.ordermanagement.Exposure;
import com.tradecloud.domain.model.ordermanagement.Order;
import com.tradecloud.domain.model.organisationalunit.OrganisationalUnit;
import com.tradecloud.domain.model.payment.ActualPaymentBasis;
import com.tradecloud.domain.model.payment.EstimatedPaymentBasis;
import com.tradecloud.domain.model.shipment.ShipmentState;
import com.tradecloud.domain.model.shipment.ShippingMode;
import com.tradecloud.domain.payment.Payable;
import com.tradecloud.domain.settlement.PlannedSettlementType;
import com.tradecloud.domain.settlement.Settleable;
import com.tradecloud.domain.shipment.AirShipment;
import com.tradecloud.domain.shipment.SeaShipment;
import com.tradecloud.domain.shipment.Shipment;
import com.tradecloud.domain.supplier.OrganisationalUnitSupplier;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang3.NotImplementedException;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.ForeignKey;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlTransient;
import java.math.BigDecimal;
import java.util.*;

/**
 * Generic CostsInvoice entity.
 */
@Entity
@Table(name = "costsinvoice")
@Inheritance(strategy = InheritanceType.JOINED)
@NamedQueries({@NamedQuery(name = "findInvoiceById",
        query = "from CostsInvoice ci left join fetch ci.costLineCosting.costLineCostingCells where ci.id=:id")})
public abstract class CostsInvoice extends Document implements Actual, Commentable<AddedCommentIncCost>, Payable, Settleable, Exposure, Costable {

    private static final long serialVersionUID = 1L;
    /*
     * Cache of costables keys and their related CostApplicationBasis values.
     */
    @Transient
    @XmlTransient
    private final Map<String, Map<CostApplicationBasis, BigDecimal>> costableToCostApplicationBasisAmountCache = new HashMap<>();
    protected boolean distributableByVolume;
    protected boolean distributableByWeight;
    /**
     * Costs invoices can be associated with Shipments or Consignments or Both!
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @XmlTransient
    protected Shipment shipment;
    @ManyToOne(fetch = FetchType.LAZY)
    @XmlTransient
    protected Consignment consignment;
    @NotNull(message = "Currency is required")
    @ManyToOne(optional = false)
    @XmlElement(name = "Currency")
    protected Currency currency;
    @NotNull(message = "grossValue is required")
    @Basic(optional = false)
    @XmlAttribute
    protected BigDecimal grossValue;
    @Temporal(TemporalType.DATE)
    @XmlAttribute
    protected Date estimatedSettlementDate;
    @NotNull
    @Basic(optional = false)
    protected BigDecimal costingCurrencyTotalValue = new BigDecimal("0.00");
    @XmlElementWrapper(name = "ActualConsignments")
    @XmlElement(name = "ActualConsignment")
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @OrderBy(value = "addedToShipmentDate")
    @Fetch(value = FetchMode.SELECT)
    private Set<ActualConsignment> actualConsignments = new LinkedHashSet<>();
    @XmlAttribute
    private BigDecimal customsDutyROE = BigDecimal.ZERO;
    private transient BigDecimal forwardRate = BigDecimal.ZERO;
    private transient BigDecimal spotRate = BigDecimal.ZERO;
    @NotNull
    @Basic(optional = false)
    private BigDecimal totalValue = new BigDecimal("0.00");
    private String additionalReference;

    /**
     * Various cost line costings to allow the costing to be broken down at each level.
     */
    @Embedded
    @XmlElement(name = "CostLineCosting")
    private CostLineCosting costLineCosting = new CostLineCosting();

    @XmlAttribute
    @Temporal(TemporalType.TIMESTAMP)
    private Date addedToConsignmentDate;

    @XmlAttribute
    @Temporal(TemporalType.TIMESTAMP)
    private Date addedToShipmentDate;

    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    @XmlElement
    @ForeignKey(name = "fk_costedTotals")
    private CostedTotals costedTotals = new CostedTotals();

    @ElementCollection(fetch = FetchType.LAZY)
    @CollectionTable(name = "costsinvoice_freetextcomments", joinColumns = {@JoinColumn(name = "costsinvoice_id", unique = false)})
    @Column(name = "reason", unique = true)
    @ForeignKey(name = "fk_invoice")
    @XmlElementWrapper(name = "FreeTextComments")
    @XmlElement(name = "FreeTextComment")
    @Fetch(value = FetchMode.SUBSELECT)
    protected List<AddedCommentIncCost> comments = new ArrayList<AddedCommentIncCost>();

    @Transient
    @XmlTransient
    private ShipmentState toShipmentState;

    @Size(max = 255, message = "invoice number exceeded 255 character")
    private String number;

    @XmlTransient
    @ElementCollection
    private Map<String, BigDecimal> costlineBondedAmount = new HashMap<String, BigDecimal>();

    private transient ExchangeRateCache exchangeRateCache = new ExchangeRateCache();

    private boolean existsInDMS;

    public CostsInvoice() {
        super();
    }

    @Override
    public Shipment getShipment() {
        return shipment;
    }

    @Override
    public Currency getCurrency() {
        return currency;
    }

    public void setCurrency(Currency currency) {
        this.currency = currency;
    }

    @Override
    public BigDecimal getGrossValue() {
        return grossValue;
    }

    public void setGrossValue(BigDecimal grossValue) {
        this.grossValue = grossValue;
    }

    public Date getEstimatedSettlementDate() {
        return estimatedSettlementDate;
    }

    public void setEstimatedSettlementDate(Date estimatedSettlementDate) {
        this.estimatedSettlementDate = estimatedSettlementDate;
    }

    @Override
    public ItemType getItemType() {
        return null;
    }

    public Date getSpotDate() {
        Shipment shipment1 = HibernateUtils.getNonProxyObject(getShipment());
        if (shipment1 != null) {
            if (shipment1.getShippingMode() == ShippingMode.SEA
                    && ((SeaShipment) shipment1).getMasterBillOfLadingDate() != null)
                return ((SeaShipment) shipment1).getMasterBillOfLadingDate();
            if (shipment1.getShippingMode() == ShippingMode.AIR
                    && ((AirShipment) shipment1).getMasterAirwayBillIssueDate() != null)
                return ((AirShipment) shipment1).getMasterAirwayBillIssueDate();

            return shipment1.getBillOfLadingDate();
        }
        return null;
    }

    public void setSpotDate(Date spotDate) {
        //not editable
    }

    public Consignment getConsignment() {
        return consignment;
    }

    public List<ActualConsignment> getActualConsignmentList() {
        return new ArrayList<>(actualConsignments);
    }

    public Set<ActualConsignment> getActualConsignments() {
        return actualConsignments;
    }

    public void setActualConsignments(Set<ActualConsignment> actualConsignments) {
        this.actualConsignments = actualConsignments;
    }

    public void addActualConsignment(ActualConsignment actualConsignment) {
        actualConsignments.add(actualConsignment);
        actualConsignment.setCostsInvoice(this);
    }

    public BigDecimal getCustomsDutyROE() {
        return customsDutyROE;
    }

    public void setCustomsDutyROE(BigDecimal customsDutyROE) {
        this.customsDutyROE = customsDutyROE;
    }

    public BigDecimal getForwardRate() {
        return forwardRate;
    }

    public void setForwardRate(BigDecimal forwardRate) {
        this.forwardRate = forwardRate;
    }

    public BigDecimal getSpotRate() {
        return spotRate;
    }

    public void setSpotRate(BigDecimal spotRate) {
        this.spotRate = spotRate;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this).append(currency).append(grossValue).append(super.toString()).toString();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        // Do not use class.equals. This can return false for proxy objects
        if (!HibernateUtils.proxyClassEquals(this, obj)) {
            return false;
        }
        CostsInvoice other = (CostsInvoice) obj;
        return new EqualsBuilder()
                .appendSuper(super.equals(obj))
                .append(currency, other.getCurrency())
                .append(grossValue, other.getGrossValue())
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .appendSuper(super.hashCode())
                .append(currency)
                .append(grossValue).toHashCode();
    }

    public ActualConsignment getConsignmentWithNumber(String number) {
        for (ActualConsignment actualConsignment : getActualConsignments()) {
            if (actualConsignment.getNumber().equals(number)) {
                return actualConsignment;
            }
        }
        return null;
    }

    @Override
    public BigDecimal getInvoiceQuantity() {
        // Empty implementation so concrete subclasses can ignore if they don't need this functionality
        return null;
    }

    @Override
    public void setInvoiceQuantity(BigDecimal invoiceQuantity) {
        // Empty implementation so concrete subclasses can ignore if they don't need this functionality
    }

    @Override
    public BigDecimal getOrderQuantity() {
        // Empty implementation so concrete subclasses can ignore if they don't need this functionality
        return null;
    }

    @Override
    public void setOrderQuantity(BigDecimal orderQuantity) {
        // Empty implementation so concrete subclasses can ignore if they don't need this functionality
    }

    @Override
    public Map<TotalsDistributionType, BigDecimal> getTotalsDistribution() {
        // Empty implementation so concrete subclasses can ignore if they don't need this functionality
        return null;
    }

    @Override
    public BigDecimal getTotalInvoiceValue() {
        return totalValue;
    }

    @Override
    public void setTotalInvoiceValue(BigDecimal totalValue) {
        this.totalValue = totalValue;
    }

    @Transient
    @Override
    public BigDecimal getTotalSettleableValue() {
        return grossValue;
    }

    public BigDecimal getCostingCurrencyTotalValue() {
        return costingCurrencyTotalValue;
    }

    public void setCostingCurrencyTotalValue(BigDecimal costingCurrencyTotalValue) {
        this.costingCurrencyTotalValue = costingCurrencyTotalValue;
    }

    /**
     * Return the weighted average forward rate from the invoice transaction currency to the costing currency.
     *
     * @return the weighted average forward rate
     */
    public abstract BigDecimal getWeightedAverageForwardRateOfExchange();

    public abstract BigDecimal getWeightedAverageSpotRateOfExchange();

    @Override
    public CostLineCosting getCostLineCosting() {
        return costLineCosting;
    }

    public void setCostLineCosting(CostLineCosting costLineCosting) {
        this.costLineCosting = costLineCosting;
    }

    public BigDecimal lookupTransactionAmount(String costLineCode, Order order) {

        CostLineCostingCell costLineCostingCell = getCostLineCostingCell(costLineCode, order);
        return costLineCostingCell != null ? costLineCostingCell.getTransactionAmount() : BigDecimal.ZERO;
    }

    public CostLineCostingCell getCostLineCostingCell(String costLineCode, Order order) {

        final List<ActualConsignment> actualConsignmentList = getActualConsignmentList();
        for (ActualConsignment actualConsignment : actualConsignmentList) {
            final List<ActualOrder> actualOrderList = actualConsignment.getActualOrderList();
            for (ActualOrder actualOrder : actualOrderList) {
                if (actualOrder.getReference().equals(order.getOrderReference()) && actualOrder.getNumber().equals(order.getNumber())) {
                    log.debug("Found matching actual order.");
                    return (CostLineCostingCell) actualOrder.getCostLineCosting().getCostingCell(costLineCode);
                }

            }
        }
        return null;
    }

    @Override
    public CostableType getCostableType() {
        return CostableType.INVOICE;
    }

    @Override
    public Costed getParent() {
        return null;
    }

    @Override
    public String getKey() {
        return new StringBuilder(getClass().getCanonicalName()).append("-").append(hashCode()).toString();
    }

    @Override
    public CostsInvoice getCostsInvoice() {
        return this;
    }

    public Map<String, Map<CostApplicationBasis, BigDecimal>> getCostableToCostApplicationBasisAmountCache() {
        return costableToCostApplicationBasisAmountCache;
    }

    public void clearCostApplicationBasisAmounts() {
        costableToCostApplicationBasisAmountCache.clear();
    }

    public List<ActualLineItem> getActualLineItems() {
        List<ActualLineItem> actualLineItems = new ArrayList<>();
        for (ActualConsignment actualConsignment : actualConsignments) {
            actualLineItems.addAll(actualConsignment.getActualItems());
        }
        return actualLineItems;

    }

    public List<ActualOrder> getActualOrders() {
        List<ActualOrder> actualOrders = new ArrayList<>();
        for (ActualConsignment actualConsignment : actualConsignments) {
            actualOrders.addAll(actualConsignment.getActualOrderList());
        }
        return actualOrders;
    }

    public Date getAddedToConsignmentDate() {
        return addedToConsignmentDate;
    }

    public void setAddedToConsignmentDate(Date addedToConsignmentDate) {
        this.addedToConsignmentDate = addedToConsignmentDate;
    }

    public Date getAddedToShipmentDate() {
        return addedToShipmentDate;
    }

    public void setAddedToShipmentDate(Date addedToShipmentDate) {
        this.addedToShipmentDate = addedToShipmentDate;
    }

    /**
     * This field will be set to true if 1 of the line items linked to the structure has a volume value.
     *
     * @return
     */
    @Override
    public boolean isDistributableByVolume() {
        return distributableByVolume;
    }

    public void setDistributableByVolume(boolean distributableByVolume) {
        this.distributableByVolume = distributableByVolume;
    }

    /**
     * This field will be set to true if 1 of the line items linked to the structure has a weight value.
     *
     * @return
     */
    @Override
    public boolean isDistributableByWeight() {
        return distributableByWeight;
    }

    public void setDistributableByWeight(boolean distributableByWeight) {
        this.distributableByWeight = distributableByWeight;
    }

    @Override
    public CostedTotals getCostedTotals() {
        if (costedTotals == null) {
            costedTotals = new CostedTotals();
        }
        return costedTotals;
    }

    @Override
    public void setCostedTotals(CostedTotals costedTotals) {
        this.costedTotals = costedTotals;
    }

    @Override
    public Costed findRootParent() {
        return this;
    }

    @Override
    public boolean isItem() {
        return false;
    }

    @Override
    public boolean isOrder() {
        return false;
    }

    @Override
    public boolean isConsignment() {
        return false;
    }

    public void setConsignment(Consignment consignment) {
        this.consignment = consignment;
    }

    @Override
    public boolean isShipment() {
        return false;
    }

    public void setShipment(Shipment shipment) {
        this.shipment = shipment;
    }

    @Override
    public boolean isCostsInvoice() {
        return true;
    }

    @Override
    public boolean isActual() {
        return true;
    }

    @Override
    public boolean match(Actual actual) {
        return false;
    }

    @Override
    public String getReferenceWithShippingRef() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public PaymentState getPaymentState() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public PlannedSettlementType getPlannedSettlementType() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public ActualPaymentBasis getActualPaymentBasis() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public EstimatedPaymentBasis getEstimatedPaymentBasis() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public EstimatedPaymentBasis getEstimatedPaymentBasis2() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public PlannedSettlementHelper getPlannedSettlementHelper() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public OrganisationalUnitSupplier getSupplier() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    public ExportParty getExportParty() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public void clearPlannedSettlements() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    public boolean isSupplierInvoice() {
        return false;
    }

    @Override
    public String getNumber() {
        return number;
    }

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

    @Override
    public BigDecimal getSellPriceInclusiveAmount() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public void setSellPriceInclusiveAmount(BigDecimal sellPriceInclusiveAmount) {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public BigDecimal getSellPriceExclusiveAmount() {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    @Override
    public void setSellPriceExclusiveAmount(BigDecimal sellPriceExclusiveAmount) {
        throw new UnsupportedOperationException(" not supported by " + this.getClass());
    }

    public ShipmentState getToShipmentState() {
        return toShipmentState;
    }

    public void setToShipmentState(ShipmentState toShipmentState) {
        this.toShipmentState = toShipmentState;
    }

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

    public SPICostlineCurrency getSpiCostlineCurrency() {
        return null;
    }

    @Override
    public Map getCostlineBondedAmount() {
        return costlineBondedAmount;
    }

    public void setCostlineBondedAmount(Map<String, BigDecimal> costlineBondedAmount) {
        this.costlineBondedAmount = costlineBondedAmount;
    }

    @Override
    public List<AddedCommentIncCost> getComments() {
        return comments;
    }

    @Override
    public void setComments(List<AddedCommentIncCost> comments) {
        this.comments = (List<AddedCommentIncCost>) comments;
    }

    @Override
    public CommentType getCommentType() {
        return CommentType.INVOICE;
    }

    public abstract String getType();

    @Override
    public Incoterm getIncoterm() {
        return shipment.getIncoterm();
    }

    @Override
    public CostingContextType getCostingContextType() {
        return CostingContextType.IMPORT;
    }

    @Override
    public Set<? extends Container> getContainers() {
        return shipment.getContainers();
    }

    @Override
    public ShippingMode getShippingMode() {
        return shipment.getShippingMode();
    }

    @Override
    public ShippingMode getMultiModalShippingMode() {
        return getShipment().getMultiModalShippingMode();
    }

    @Override
    public OrganisationalUnit getOrganisationalUnit() {
        return null;
    }

    @Override
    public List<? extends PersistenceBase> getLineItems() {
        return shipment.getLineItems();
    }

    @Override
    public Date getArrivalDateAtPlaceOfDischarge() {
        return shipment.getArrivalDateAtPlaceOfDischarge();
    }

    @Override
    public CostableCostDefinition getCostableCostDefinition() {
        return shipment.getCostableCostDefinition();
    }

    @Override
    public void setCostableCostDefinition(CostableCostDefinition costableCostDefinition) {
        this.shipment.setCostableCostDefinition(costableCostDefinition);
    }

    public BigDecimal getTotalValue() {
        return totalValue;
    }

    public void setTotalValue(BigDecimal totalValue) {
        this.totalValue = totalValue;
    }

    public void setExchangeRateCache(ExchangeRateCache exchangeRateCache) {
        this.exchangeRateCache = exchangeRateCache;
    }

    public ExchangeRateCache getExchangeRateCache() {
        return exchangeRateCache;
    }

    public boolean isExistsInDMS() {
        return existsInDMS;
    }

    public void setExistsInDMS(boolean existsInDMS) {
        this.existsInDMS = existsInDMS;
    }

    public ExportCosting getExportCosting() {
        throw new NotImplementedException("please implement");
    }

    public String getAdditionalReference() {
        return additionalReference;
    }

    public void setAdditionalReference(String additionalReference) {
        this.additionalReference = additionalReference;
    }

}