BaseServiceProviderInvoice.java

package com.tradecloud.domain.document.invoice;

import com.tradecloud.common.base.HibernateUtils;
import com.tradecloud.domain.base.utils.MathUtils;
import com.tradecloud.domain.common.Currency;
import com.tradecloud.domain.configuration.SPICostlineCurrency;
import com.tradecloud.domain.costing.clean.CostLineNames;
import com.tradecloud.domain.costing.clean.Costed;
import com.tradecloud.domain.costing.clean.CostingVisitor;
import com.tradecloud.domain.document.CreditNote;
import com.tradecloud.domain.document.DocumentType;
import com.tradecloud.domain.document.SettleableDocument;
import com.tradecloud.domain.duties.CustomsDutyCaptureLevel;
import com.tradecloud.domain.party.ServiceProvider;
import com.tradecloud.domain.settlement.*;
import com.tradecloud.domain.supplier.Creditor;
import org.apache.commons.collections4.keyvalue.MultiKey;
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.apache.commons.lang3.ObjectUtils;
import org.apache.log4j.Logger;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
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.*;

/**
 * Created by ds on 2015/12/10.
 */
@MappedSuperclass
public abstract class BaseServiceProviderInvoice extends CostsInvoice implements SettleableDocument {

    @Transient
    @XmlTransient
    private Logger log = Logger.getLogger(BaseServiceProviderInvoice.class);
    private static final long serialVersionUID = 1L;

    private BigDecimal forwardRate;

    @NotNull(message = "SPI serviceProvider is required")
    @ManyToOne
    @XmlElement(name = "ServiceProvider")
    private ServiceProvider serviceProvider;

    @NotNull(message = "SPI nettValue is required")
    @XmlAttribute
    private BigDecimal nettValue;

    @XmlAttribute
    private BigDecimal taxValue = BigDecimal.ZERO;

    @NotNull(message = "SPI customsDutyCaptureLevel is required")
    @XmlAttribute(required = true)
    @Enumerated(EnumType.STRING)
    private CustomsDutyCaptureLevel customsDutyCaptureLevel = CustomsDutyCaptureLevel.INVOICE;

    @NotNull(message = "SPI settlementDateCalculationType is required")
    @XmlAttribute(required = true)
    @Enumerated(EnumType.STRING)
    private SettlementDateCalculationType settlementDateCalculationType;

    /*
     * Internal use only
     */
    @OneToMany(cascade = CascadeType.ALL)
    @XmlElementWrapper(name = "Payments")
    @XmlElement(name = "Payment")
    private Set<Payment> payments = new HashSet<>();

    @OneToMany(cascade = CascadeType.ALL)
    @XmlElementWrapper(name = "IndirectCostAllocations")
    @XmlElement(name = "IndirectCostAllocation")
    private Set<IndirectCostAllocation> indirectCostsAllocations = new TreeSet<>();

    @OneToMany(cascade = CascadeType.ALL)
    @XmlElementWrapper(name = "CreditNotes")
    @XmlElement(name = "CreditNote")
    private Set<CreditNote> creditNotes = new HashSet<>();

    @OneToMany
    @XmlElementWrapper(name = "PlannedSettlements")
    @XmlElement(name = "PlannedSettlement")
    private Set<PlannedSettlement> plannedSettlements;

    @Enumerated(value = EnumType.STRING)
    @XmlAttribute
    @NotNull(message = "SPI spiCostlineCurrency is required")
    private SPICostlineCurrency spiCostlineCurrency = SPICostlineCurrency.FROM_COST_DEFINITION;

    private BigDecimal weightedAverageForwardRateOfExchange;

    public ServiceProvider getServiceProvider() {
        return serviceProvider;
    }

    public void setServiceProvider(ServiceProvider serviceProvider) {
        this.serviceProvider = serviceProvider;
    }

    public BigDecimal getNettValue() {
        return nettValue;
    }

    public void setNettValue(BigDecimal nettValue) {
        this.nettValue = nettValue;
    }

    public BigDecimal getTaxValue() {
        return taxValue;
    }

    public void setTaxValue(BigDecimal taxValue) {
        this.taxValue = taxValue;
    }

    public Set<Payment> getPayments() {
        return payments;
    }

    public void setPayments(Set<Payment> payments) {
        this.payments = payments;
    }

    public Set<IndirectCostAllocation> getIndirectCostsAllocations() {
        return indirectCostsAllocations;
    }

    public void setIndirectCostsAllocations(Set<IndirectCostAllocation> indirectCostsAllocations) {
        this.indirectCostsAllocations = indirectCostsAllocations;
    }

    public Set<CreditNote> getCreditNotes() {
        return creditNotes;
    }

    public void setCreditNotes(Set<CreditNote> creditNotes) {
        this.creditNotes = creditNotes;
    }

    public SettlementDateCalculationType getSettlementDateCalculationType() {
        return settlementDateCalculationType;
    }

    public void setSettlementDateCalculationType(SettlementDateCalculationType settlementDateCalculationType) {
        this.settlementDateCalculationType = settlementDateCalculationType;
    }

    @Override
    public String getTypeDesc() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public boolean hasPlannedSettlements() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void addPlannedSettlement(PlannedSettlement settlement) {
        // TODO Auto-generated method stub

    }

    @Override
    public Date resolveRateDate(RateDateSource source) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public DocumentType getDocumentType() {
        return DocumentType.SERVICE_PROVIDER_INVOICE;
    }

    @Override
    public Creditor getCreditor() {
        return serviceProvider;
    }

    @Override
    public Set<PlannedSettlement> getPlannedSettlements() {
        return plannedSettlements;
    }

    @Override
    public void setPlannedSettlements(Set<PlannedSettlement> plannedSettlements) {
        this.plannedSettlements = plannedSettlements;
    }

    public CustomsDutyCaptureLevel getCustomsDutyCaptureLevel() {
        return customsDutyCaptureLevel;
    }

    public void setCustomsDutyCaptureLevel(CustomsDutyCaptureLevel customsDutyCaptureLevel) {
        this.customsDutyCaptureLevel = customsDutyCaptureLevel;
    }

    @Override
    public void accept(CostingVisitor costingVisitor) {
        for (ActualConsignment actualConsignment : getActualConsignments()) {
            actualConsignment.accept(costingVisitor);
        }
        if (costingVisitor != null) {
            costingVisitor.visit(this);
        } else {
            log.error("Null CostingVisitor passed to ActualConsignment: " + getId() + ", " + getReference());
        }
    }

    @Override
    public void acceptVisitParentFirst(CostingVisitor costingVisitor) {
        costingVisitor.visit(this);
        for (ActualConsignment actualConsignment : getActualConsignments()) {
            actualConsignment.acceptVisitParentFirst(costingVisitor);
        }
    }

    @Override
    public List<Costed> getCostedChildren() {
        return new ArrayList<Costed>(getActualConsignments());
    }

    @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;
        }
        BaseServiceProviderInvoice other = (BaseServiceProviderInvoice) obj;

        return new EqualsBuilder()
                .appendSuper(super.equals(obj))
                .append(serviceProvider, other.getServiceProvider())
                .append(reference, other.getReference())
                .append(shipment, other.getShipment())
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .appendSuper(super.hashCode())
                .append(serviceProvider)
                .append(reference)
                .append(shipment)
                .toHashCode();
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this).appendSuper(super.toString()).append(nettValue).append(taxValue).append(customsDutyCaptureLevel).append
                (serviceProvider != null ? serviceProvider.getName() : null).append(settlementDateCalculationType).toString();
    }

    @Override
    public Object getTraversalKey() {
        return new MultiKey(ServiceProviderInvoice.class, reference);
    }

    @Override
    public BigDecimal getWeightedAverageForwardRateOfExchange() {
        if (weightedAverageForwardRateOfExchange == null) {
            weightedAverageForwardRateOfExchange = calculateAvgFwdRate(null);
        }
        return weightedAverageForwardRateOfExchange;
    }

    public BigDecimal calculateAvgFwdRate(Currency costingCurrency) {
        if (costingCurrency != null && getSpiCostlineCurrency() != SPICostlineCurrency.MIXED_CURRENCIES
                && this.getCurrency().equals(costingCurrency)) {
            return BigDecimal.ONE;
        } else {
            List<ActualOrder> actualOrders = getActualOrders();
            BigDecimal costingCurrencyTotal = BigDecimal.ZERO;
            BigDecimal currencyTotal = BigDecimal.ZERO;

            for (ActualOrder actualOrder : actualOrders) {
                for (CostLineCostingCell costLineCostingCell : actualOrder.getCostLineCosting().getCostLineCostingCellsList()) {
                    if (costLineCostingCell.getTransactionAmount() != null && costLineCostingCell.getForwardRate() != null) {
                        costingCurrencyTotal = costingCurrencyTotal.add(costLineCostingCell.getTransactionAmount().multiply(costLineCostingCell.
                                getForwardRate()));
                        currencyTotal = currencyTotal.add(costLineCostingCell.getTransactionAmount());
                    }
                }
            }
            return MathUtils.safeDivide(costingCurrencyTotal, currencyTotal);
        }
    }

    public BigDecimal getWeightedAverageForwardRateOfExchange(Currency costingCurrency) {
        if (getSpiCostlineCurrency() != SPICostlineCurrency.MIXED_CURRENCIES && this.getCurrency().equals(costingCurrency)) {
            return BigDecimal.ONE;
        } else {
            return getWeightedAverageForwardRateOfExchange();
        }

    }

    @Override
    public BigDecimal getWeightedAverageSpotRateOfExchange() {
        throw new NotImplementedException("getWeightedAverageSpotRateOfExchange not implemented for base Service provider invoice");
    }

    @Override
    public BigDecimal getCostingCurrencyTotalValue() {
        if (BigDecimal.ZERO.compareTo(costingCurrencyTotalValue) == 0) {
            // calculate the costing currency total
            setCostingCurrencyTotalValue(calculateCostingCurrencyTotalValue());
        }
        return costingCurrencyTotalValue;
    }

    /**
     * Internal method to set the costingCurrencyTotalValue of this invoice.
     *
     * @return
     */
    public BigDecimal calculateCostingCurrencyTotalValue() {
        BigDecimal costingCurrencyTotal = BigDecimal.ZERO;
        // need to total all the cost lines at this level and use the associated forward rate
        List<ActualOrder> actualOrders = getActualOrders();
        for (ActualOrder actualOrder : actualOrders) {
            for (CostLineCostingCell costLineCostingCell : actualOrder.getCostLineCosting().getCostLineCostingCellsList()) {
                if (costLineCostingCell.getTransactionAmount() != null && costLineCostingCell.getForwardRate() != null) {
                    costingCurrencyTotal = costingCurrencyTotal.add(costLineCostingCell.getTransactionAmount().multiply(costLineCostingCell.
                            getForwardRate()));
                }
            }
        }
        return costingCurrencyTotal;
    }

    public BigDecimal getForwardRate() {
        return forwardRate;
    }

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

    public BigDecimal calculateVat() {
        List<ActualOrder> actualOrders = getActualOrders();
        return calculateVat(actualOrders, false);
    }

    private BigDecimal calculateVat(List<? extends Actual> actuals, boolean integrateVat) {
        BigDecimal vat = BigDecimal.ZERO;
        for (Actual actual : actuals) {
            List<CostLineCostingCell> costLineCostingCells = actual.getCostLineCosting().getCostLineCostingCells();
            for (CostLineCostingCell costLineCostingCell : costLineCostingCells) {
                BigDecimal cellVat = integrateVat ? costLineCostingCell.getIntegratedVat() : costLineCostingCell.getVat();
                vat = vat.add(ObjectUtils.firstNonNull(cellVat, BigDecimal.ZERO));
            }
        }
        return vat;
    }

    public BigDecimal getNetLocalCustomsVat() {
        CostingCell costingCell = getCostLineCosting().getCostingCell(CostLineNames.CUSTOMS_VAT);
        if (costingCell != null) {
            return costingCell.getTransactionAmount();
        }
        return BigDecimal.ZERO;
    }

    public abstract BigDecimal getNetLocalVatable();

    public abstract BigDecimal getNetLocalNonVatable();

    public BigDecimal calculateIntegratedVat() {
        return calculateVat(Collections.singletonList(this), true);
    }

    public abstract String getSplitInvoiceReference();

    public SPICostlineCurrency getSpiCostlineCurrency() {
        return spiCostlineCurrency;
    }

    public void setSpiCostlineCurrency(SPICostlineCurrency spiCostlineCurrency) {
        this.spiCostlineCurrency = spiCostlineCurrency;
    }

    public List<PlannedSettlement> getPlannedSettlementList() {
        return new ArrayList<>(plannedSettlements);
    }

    public InvoiceType getInvoiceType() {
        return InvoiceType.OTHER;
    }

    public boolean isGenerated() {
        return false;
    }
}