PlannedSettlement.java

package com.tradecloud.domain.settlement;

import com.tradecloud.domain.base.utils.DateUtils;
import com.tradecloud.domain.base.utils.MathUtils;
import com.tradecloud.domain.common.Currency;
import com.tradecloud.domain.document.PaymentState;
import com.tradecloud.domain.document.invoice.CostsInvoice;
import com.tradecloud.domain.document.invoice.PlannedSettlementHelper;
import com.tradecloud.domain.exception.InvalidEntityException;
import com.tradecloud.domain.model.ordermanagement.Order;
import com.tradecloud.domain.model.payment.ActualPaymentBasis;
import com.tradecloud.domain.model.payment.EstimatedPaymentBasis;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
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.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;

@Entity
@Table(name = "plannedsettlement")
public class PlannedSettlement extends PlannedSettlementDetail implements Cloneable {

    private static final long serialVersionUID = 1L;
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat();

    static {
        dateFormat.applyPattern("dd-MM-yyyy");
    }

    @Enumerated(EnumType.STRING)
    @XmlAttribute
    private PlannedSettlementType type;

    @XmlAttribute
    private boolean active;

    @XmlAttribute
    private boolean formerlyExposed;

    @ManyToOne
    @XmlElement(name = "EstimatedPaymentBasis")
    private EstimatedPaymentBasis estimatedPaymentBasis;

    @ManyToOne
    @XmlElement(name = "ActualPaymentBasis")
    private ActualPaymentBasis actualPaymentBasis;

    @XmlAttribute
    private BigDecimal amountInCostingCurrency;

    /**
     * Probably a bad name for it now as i'm not really using that pattern.
     * Really what this object represents is the system default planned settlement
     */
    @OneToOne(cascade = CascadeType.ALL)
    @Deprecated
    //we don't use of this object , we should remove it.
    private PlannedSettlement plannedSettlementMemento;

    /**
     * Persist the default value so we can tell if it's been edited since creation.
     */
    private String defaultValue;

    @Enumerated(EnumType.STRING)
    @XmlAttribute
    private SettlementTreasuryState treasuryState;

    @XmlElementWrapper(name = "Payments")
    @XmlElement(name = "Payment")
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "plannedSettlement")
    @Fetch(value = FetchMode.SUBSELECT)
    private Set<Payment> payments = new LinkedHashSet<Payment>();

    @XmlElementWrapper(name = "PlannedSettlementOrders")
    @XmlElement(name = "PlannedSettlementOrder")
    @ForeignKey(name = "fk_plannedSettlementorder")
    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH, CascadeType.REFRESH}, mappedBy = "plannedSettlement",
            orphanRemoval = true, fetch = FetchType.LAZY)
    @OrderBy(value = "addedToConsignmentDate")
    private Set<PlannedSettlementOrder> plannedSettlementOrders = new LinkedHashSet<PlannedSettlementOrder>();

    @NotNull
    @XmlAttribute
    protected Integer days;

    /**
     * Persist the state of the plannedSettlement.
     * DEFAULT unsettled
     */
    @Enumerated(EnumType.STRING)
    @XmlAttribute
    private PaymentState paymentState;

    private String label;
    @ManyToOne(fetch = FetchType.LAZY)
    private Order order;
    @ManyToOne(fetch = FetchType.LAZY)
    private CostsInvoice invoice;

    public static PlannedSettlementBuilder createBuilder() {
        return new PlannedSettlementBuilder();
    }

    /**
     * Constructor required by the framework. The PlannedSettlementBuilder should be used instead.
     */
    public PlannedSettlement() {
    }

    public PlannedSettlement(PlannedSettlementDetail detail, Integer days, PlannedSettlementType type, boolean active, boolean formerlyExposed,
                             EstimatedPaymentBasis estimatedPaymentBasis, ActualPaymentBasis actualPaymentBasis, BigDecimal amountInCostingCurrency,
                             SettlementTreasuryState treasuryState, PaymentState paymentState) {
        super(detail);
        this.type = type;
        this.active = active;
        this.formerlyExposed = formerlyExposed;
        this.days = days;
        this.estimatedPaymentBasis = estimatedPaymentBasis;
        this.actualPaymentBasis = actualPaymentBasis;
        this.amountInCostingCurrency = amountInCostingCurrency;
        this.treasuryState = treasuryState;
        this.paymentState = paymentState;
    }

    /**
     * We need to know if the Planned Settlement has been edited from it's default version.
     *
     * Different rules apply if it has been edited by a user.
     *
     * Set the default value in the builder at creation time. If it's different then it's been changed. Simple as.
     *
     * @return true if it still has all the default values and hasn't been edited.
     */
    public boolean isDefault() {
        //return this.equals(getPlannedSettlementMemento());
        return !(overriddenAmount || overriddenForwardRate || overriddenSettlementDate || overriddenSpotRate || overriddenPercentage);
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this).append(type).append(active).append(formerlyExposed).append(amount).append(estimatedPaymentBasis)
                .append(actualPaymentBasis).append(settlementDate).append(forwardRate).append(spotRate).append(amountInCostingCurrency)
                .append(treasuryState).append(defaultValue).toString();
    }

    public PaymentState getPaymentState() {
        return paymentState;
    }

    public void setPaymentState(PaymentState paymentState) {
        this.paymentState = paymentState;
    }

    public PlannedSettlementType getType() {
        return type;
    }

    public void setType(PlannedSettlementType type) {
        this.type = type;
    }

    public EstimatedPaymentBasis getEstimatedPaymentBasis() {
        return estimatedPaymentBasis;
    }

    public void setEstimatedPaymentBasis(EstimatedPaymentBasis estimatedPaymentBasis) {
        this.estimatedPaymentBasis = estimatedPaymentBasis;
    }

    public ActualPaymentBasis getActualPaymentBasis() {
        return actualPaymentBasis;
    }

    public void setActualPaymentBasis(ActualPaymentBasis actualPaymentBasis) {
        this.actualPaymentBasis = actualPaymentBasis;
    }

    public String getFormattedSettlementDate() {
        return dateFormat.format(settlementDate);
    }

    public String getDefaultValue() {
        return defaultValue;
    }

    public void setDefaultValue(String defaultValue) {
        this.defaultValue = defaultValue;
    }

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

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

    public Set<PlannedSettlementOrder> getPlannedSettlementOrders() {
        return plannedSettlementOrders;
    }

    public void setPlannedSettlementOrders(Set<PlannedSettlementOrder> plannedSettlementOrders) {
        this.plannedSettlementOrders = plannedSettlementOrders;
    }

    /**
     * Checks the value of the settlement date against what the system thinks the default value should be.
     * The default value is held in the memento
     *
     * @return
     */
    public boolean isSettlementDateOutOfSynch() {
        return getPlannedSettlementMemento() != null && getPlannedSettlementMemento().getSettlementDate().compareTo(getSettlementDate()) != 0;
    }

    public BigDecimal getAmountInCostingCurrencyUsingSpotRate() {
        BigDecimal amountInCostingCurrencyUsingSpotRate = BigDecimal.ONE;
        if (amount == null || spotRate == null) {
            return BigDecimal.ZERO;
        }
        if (plannedSettlementOrders == null || plannedSettlementOrders.isEmpty()) {
//           return this.getAmount().multiply(getSpotRate());
            return MathUtils.multiply(this.getAmount(), getSpotRate());
        }
        PlannedSettlementHelper plannedSettlementHelper = new PlannedSettlementHelper(Collections.singleton(this));
        return plannedSettlementHelper.getOrderCostingCurrencyTotalValueUsingSpotRate();

    }

    public BigDecimal getParentAmountInCostingSpotRate() {
        BigDecimal amountInCostingCurrencyUsingSpotRate = BigDecimal.ONE;
        if (spotRate != null) {
            amountInCostingCurrencyUsingSpotRate = MathUtils.multiply(this.getAmount(), getSpotRate());
        }
        return amountInCostingCurrencyUsingSpotRate;
    }

    public void setAmountInCostingCurrency(BigDecimal amountInCostingCurrency) {
        this.amountInCostingCurrency = amountInCostingCurrency;
    }

    public SettlementTreasuryState getTreasuryState() {
        return treasuryState;
    }

    public void setTreasuryState(SettlementTreasuryState treasuryState) {
        this.treasuryState = treasuryState;
    }

    @Override
    public Boolean getActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }

    public void setFormerlyExposed(boolean formerlyExposed) {
        this.formerlyExposed = formerlyExposed;
    }

    /**
     * Does this settlement have any treasury implications.
     *
     * @return
     */
    public boolean hasTreasuryImplication() {
        return isFormerlyExposed(); // getTreasuryState().equals(SettlementTreasuryState.NEW);
    }

    /**
     * has this settlement ever been exposed to treasury?
     *
     * @return
     */
    public boolean isFormerlyExposed() {
        return formerlyExposed;
    }

    @Override
    public Integer getDays() {
        return days;
    }

    public void setDays(Integer days) {
        this.days = days;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(getForwardRate()).append(getSpotRate()).append(getSettlementDate()).append(days).
                append(getSplitPaymentType()).toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof PlannedSettlement)) {
            return false;
        }
        PlannedSettlement other = (PlannedSettlement) obj;
        // need to put some tolerance when dealing with numbers.
        // without tolerance BigDecimal of 2.00 is not equals to 2.00000
        return MathUtils.areNumbersCloselyEqual(amount, other.getAmount(), 4)
                && MathUtils.areNumbersCloselyEqual(forwardRate, other.getForwardRate(), 4)
                && MathUtils.areNumbersCloselyEqual(spotRate, other.getSpotRate(), 4)
                && new EqualsBuilder().append(DateUtils.toT_YMDFormat(settlementDate), DateUtils.toT_YMDFormat(other.getSettlementDate()))
                .append(days, other.days).append(getSplitPaymentType(), other.getSplitPaymentType()).isEquals();
    }

    @Deprecated
    //we don't use of this object , we should it.
    public PlannedSettlement getPlannedSettlementMemento() {
        return plannedSettlementMemento;
    }

    public void setPlannedSettlementMemento(PlannedSettlement plannedSettlementMemento) {
        this.plannedSettlementMemento = plannedSettlementMemento;
    }

    @Override
    public PlannedSettlement clone() {
        PlannedSettlement clone = (PlannedSettlement) super.clone();
        return clone;
    }

    @Override
    @Transient
    public BigDecimal getAmountInCostingCurrency() {
        if (amount == null || forwardRate == null) {
            return BigDecimal.ZERO;
        }
        if (plannedSettlementOrders == null || plannedSettlementOrders.isEmpty()) {
            return MathUtils.multiply(amount, getForwardRate());
        } else {
            PlannedSettlementHelper plannedSettlementHelper = new PlannedSettlementHelper(Collections.singleton(this));
            return plannedSettlementHelper.getOrderCostingCurrencyTotalValue();
        }
    }

    public void calculateCostingAmount() {
        this.amountInCostingCurrency = MathUtils.multiply(forwardRate, amount);
    }

    public BigDecimal getParentAmountInCostingCurrency() {
        if (amount == null || forwardRate == null) {
            return BigDecimal.ZERO;
        }
        return MathUtils.multiply(amount, getForwardRate());

    }

    public void addPlannedSettlementOrder(PlannedSettlementOrder plannedSettlementOrder) {
        this.plannedSettlementOrders.add(plannedSettlementOrder);
    }

    public static final class PlannedSettlementBuilder {

        private PlannedSettlementType type;
        private boolean active;
        private boolean formerlyExposed;
        private BigDecimal amount;
        private EstimatedPaymentBasis estimatedPaymentBasis;
        private ActualPaymentBasis actualPaymentBasis;
        private Date settlementDate;
        private BigDecimal forwardRate;
        private BigDecimal spotRate;
        private BigDecimal amountInCostingCurrency;
        private SettlementTreasuryState treasuryState;
        private Currency currency;
        private Integer days;
        private PaymentState paymentState;
        private String number;
        private Order order;
        private CostsInvoice invoice;

        /**
         * Private constructor to prevent instantiation.
         */
        private PlannedSettlementBuilder() {
        }

        public PlannedSettlement build() {
            // Calculate this value
            validate();
            calculateCostingAmount();
            PlannedSettlementDetail detail = new PlannedSettlementDetail();
            detail.setAmount(amount);
            detail.setSettlementDate(settlementDate);
            detail.setForwardRate(forwardRate);
            detail.setSpotRate(spotRate);
            detail.setCurrency(currency);
            detail.setNumber(number);
            PlannedSettlement ps =
                    new PlannedSettlement(detail, days, type, active, formerlyExposed, estimatedPaymentBasis, actualPaymentBasis,
                            amountInCostingCurrency, treasuryState, paymentState);
            ps.setPlannedSettlementMemento(ps.clone());
            ps.setOrder(order);
            ps.setInvoice(invoice);
            return ps;
        }

        private void calculateCostingAmount() {
            this.amountInCostingCurrency = MathUtils.multiplyVA(forwardRate, amount);
        }

        private void validate() {
            if (amount == null) {
                throw new InvalidEntityException("Amount cannot be null");
            }
            if (forwardRate == null) {
                throw new InvalidEntityException("Forward Rate cannot be null");
            }
            if (settlementDate == null) {
                throw new InvalidEntityException("Settlement Date cannot be null");
            }
            if (days == null) {
                throw new InvalidEntityException("Days cannot be null");
            }
        }

        public PlannedSettlementBuilder setPlannedSettlementType(PlannedSettlementType type) {
            this.type = type;
            return this;
        }

        public PlannedSettlementBuilder setActive(boolean active) {
            this.active = active;
            return this;
        }

        public PlannedSettlementBuilder setFormerlyExposed(boolean formerlyExposed) {
            this.formerlyExposed = formerlyExposed;
            return this;
        }

        public PlannedSettlementBuilder setAmount(BigDecimal amount) {
            this.amount = amount;
            return this;
        }

        public PlannedSettlementBuilder setEstimatedPaymentBasis(EstimatedPaymentBasis estimatedPaymentBasis) {
            this.estimatedPaymentBasis = estimatedPaymentBasis;
            return this;
        }

        public PlannedSettlementBuilder setActualPaymentBasis(ActualPaymentBasis actualPaymentBasis) {
            this.actualPaymentBasis = actualPaymentBasis;
            return this;
        }

        public PlannedSettlementBuilder setSettlementDate(Date settlementDate) {
            this.settlementDate = settlementDate;
            return this;
        }

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

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

        public PlannedSettlementBuilder setAmountInCostingCurrency(BigDecimal amountInCostingCurrency) {
            this.amountInCostingCurrency = amountInCostingCurrency;
            return this;
        }

        public PlannedSettlementBuilder setTreasuryState(SettlementTreasuryState treasuryState) {
            this.treasuryState = treasuryState;
            return this;
        }

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

        public PlannedSettlementBuilder setDays(Integer days) {
            this.days = days;
            return this;
        }

        public PlannedSettlementBuilder setInvoice(CostsInvoice invoice) {
            this.invoice = invoice;
            return this;
        }

        public PlannedSettlementBuilder setOrder(Order order) {
            this.order = order;
            return this;
        }

        public PaymentState getPaymentState() {
            return paymentState;
        }

        public void setPaymentState(PaymentState paymentState) {
            this.paymentState = paymentState;
        }

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

    public CostsInvoice getInvoice() {
        return invoice;
    }

    public void setInvoice(CostsInvoice invoice) {
        this.invoice = invoice;
    }

    public Order getOrder() {
        return order;
    }

    public void setOrder(Order order) {
        this.order = order;
    }
}