ContainerUsage.java

package com.tradecloud.domain.container;

import com.tradecloud.common.base.PersistenceBase;
import com.tradecloud.domain.model.ordermanagement.Order;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.springframework.stereotype.Component;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.xml.bind.annotation.*;
import java.math.BigDecimal;
import java.util.Optional;

/**
 * Container Usage - How a container will be used by an order. The user will be able to specify the order ratio that will be packed into a container.
 * If you only have one order linked to a consignment, the order will appear below each container to allow a user to specify the ratio of each order
 * into the various container types. If you have multiple orders linked to a consignment, the user will be able to specify the ratio of different
 * order that will be packed into a container. 'Order number and Order references'. Display both the order number and the order reference separated by
 * a hyphen are highlighted in red in the screenshot below. The container type FCL20GP is filled by 0.29 of order 042438 (system generated number)
 * 4300031803 (po ref) and 0.71 of order 042259 (system generated number) 12/0683A (po ref).
 */
@Entity
@Component(value = "containerusage")
@Table(name = "containerusage")
@Access(AccessType.FIELD)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "ContainerUsage")
public class ContainerUsage extends PersistenceBase implements Comparable<ContainerUsage> {

    private static final long serialVersionUID = 1L;

    @ManyToOne(fetch = FetchType.LAZY)
    @NotNull
    @XmlIDREF
    @XmlAttribute(name = "OrderNumber")
    private Order order;

    @ManyToOne
    @JoinColumn(name = "container_id")
    @XmlTransient
    private Container container;

    private BigDecimal volumem3;
    private BigDecimal weightKG;
    private BigDecimal chargeableWeightKG;

    private boolean volumeOverridden;

    private boolean weightOverridden;

    @XmlElement
    protected BigDecimal cartonsPerContainer;

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

    /**
     * The quantity/proportion of the container that will be utilized by each order linked to the consignment.
     */
    private BigDecimal numberAtPOD = new BigDecimal("0.0000");

    /**
     * Indicates if the container will first go to a clearing depot.
     */
    private BigDecimal numberAtClearingDepot;

    /**
     * Indicates if the container will go directly to a final destination and not to a clearing depot.
     * <p>
     * If the user changed the Number at clearing depot field, the Number directly to Final Destination field
     * should be recalculated as follows: Number directly to FD = (Number at POD - Number at Clearing depot)
     */
    private BigDecimal numberDirectlyToFinalDestination;

    /**
     * The days that the container will be held in storage.
     */
    private BigDecimal numberOfDaysAtStorage;

    public ContainerUsage() {

    }

    public ContainerUsage(Order order, Container container, BigDecimal numberAtPOD, BigDecimal numberAtClearingDepot,
                          BigDecimal numberDirectlyToFinalDestination, BigDecimal numberOfDaysAtStorage) {
        this.order = order;
        this.container = container;
        this.numberAtPOD = numberAtPOD;
        this.numberAtClearingDepot = numberAtClearingDepot;
        this.numberDirectlyToFinalDestination = numberDirectlyToFinalDestination;
        this.numberOfDaysAtStorage = numberOfDaysAtStorage;
        calculateAndSetCartonsPerContainer();
    }

    public ContainerUsage(Order order, Container container, BigDecimal numberAtPOD, BigDecimal numberAtClearingDepot,
                          BigDecimal numberDirectlyToFinalDestination, BigDecimal numberOfDaysAtStorage, BigDecimal volumem3, BigDecimal weightKG,
                          BigDecimal chargeableWeightKG) {
        this.order = order;
        this.container = container;
        this.numberAtPOD = numberAtPOD;
        this.numberAtClearingDepot = numberAtClearingDepot;
        this.numberDirectlyToFinalDestination = numberDirectlyToFinalDestination;
        this.numberOfDaysAtStorage = numberOfDaysAtStorage;

        this.volumem3 = volumem3;
        this.weightKG = weightKG;
        this.chargeableWeightKG = chargeableWeightKG;
        calculateAndSetCartonsPerContainer();
    }

    public Container getContainer() {
        return container;
    }

    public void setContainer(Container container) {
        this.container = container;
    }

    public Order getOrder() {
        return order;
    }

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

    public BigDecimal getNumberAtPOD() {
        return numberAtPOD;
    }

    public void setNumberAtPOD(BigDecimal numberAtPOD) {
        this.numberAtPOD = numberAtPOD;
        if (numberAtClearingDepot != null) {
            recalculateNumberDirectlyToFinalDestination(numberAtClearingDepot);
        }
    }

    public BigDecimal getNumberAtClearingDepot() {
        return numberAtClearingDepot;
    }

    /**
     * If the user changed the numberAtClearingDepot field, the numberDirectlyToFinalDestination field
     * should be recalculated as follows: Number directly to FD = (Number at POD - Number at Clearing depot).
     *
     * @param numberAtClearingDepot
     */
    public void setNumberAtClearingDepot(BigDecimal numberAtClearingDepot) {
        if (this.numberAtClearingDepot != null && !this.numberAtClearingDepot.equals(numberAtClearingDepot)) {
            recalculateNumberDirectlyToFinalDestination(numberAtClearingDepot);
        }
        this.numberAtClearingDepot = numberAtClearingDepot;
    }

    private void recalculateNumberDirectlyToFinalDestination(BigDecimal numberAtClearingDepotParam) {
        if (numberAtPOD != null) {
            this.numberDirectlyToFinalDestination = numberAtPOD.subtract(numberAtClearingDepotParam);
        }
    }

    public BigDecimal getNumberDirectlyToFinalDestination() {
        return numberDirectlyToFinalDestination;
    }

    public void setNumberDirectlyToFinalDestination(BigDecimal numberDirectlyToFinalDestination) {
        this.numberDirectlyToFinalDestination = numberDirectlyToFinalDestination;
    }

    public void setNumberOfDaysAtStorage(BigDecimal numberOfDaysAtStorage) {
        this.numberOfDaysAtStorage = numberOfDaysAtStorage;
    }

    public BigDecimal getNumberOfDaysAtStorage() {
        return numberOfDaysAtStorage;
    }

    public BigDecimal getVolumem3() {
        return volumem3;
    }

    public void setVolumem3(BigDecimal volumem3) {
        if(!volumeOverridden) {
            this.volumem3 = volumem3;
        }
    }

    public void setVolumem3NoOverride(BigDecimal volumem3) {
        this.volumem3 = volumem3;
    }

    public BigDecimal getWeightKG() {
        return weightKG;
    }

    public void setWeightKG(BigDecimal weightKG) {
        if(!weightOverridden) {
            this.weightKG = weightKG;
        }
    }

    public void setWeightKGNoOverride(BigDecimal weightKG) {
        this.weightKG = weightKG;
    }

    public BigDecimal getChargeableWeightKG() {
        return chargeableWeightKG;
    }

    public void setChargeableWeightKG(BigDecimal chargeableWeightKG) {
        this.chargeableWeightKG = chargeableWeightKG;
    }

    @Override
    public int hashCode() {
        HashCodeBuilder builder = new HashCodeBuilder();

        if (container != null) {
            builder.append(container.getId());
        }

        if (order != null) {
            builder.append(order.getId());
        }
        builder.append(numberAtClearingDepot).append(numberAtPOD).append(numberDirectlyToFinalDestination).append(numberOfDaysAtStorage);

        return builder.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        ContainerUsage other = (ContainerUsage) obj;
        EqualsBuilder builder = new EqualsBuilder();
        builder.append(order, other.getOrder()).append(container, other.getContainer())
                .append(numberAtClearingDepot, other.getNumberAtClearingDepot()).append(numberAtPOD, other.getNumberAtPOD())
                .append(numberDirectlyToFinalDestination, other.getNumberDirectlyToFinalDestination())
                .append(numberOfDaysAtStorage, other.getNumberOfDaysAtStorage()).append(volumem3, other.getVolumem3())
                .append(weightKG, other.getWeightKG()).append(chargeableWeightKG, other.getChargeableWeightKG());

        return builder.isEquals();
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this).append("container", container).append("numberAtPOD", numberAtPOD)
                .append("numberAtClearingDepot", numberAtClearingDepot).append("numberDirectlyToFinalDestination", numberDirectlyToFinalDestination)
                .append("numberOfDaysAtStorage", numberOfDaysAtStorage).append("volumem3", volumem3).append("weightKG", weightKG)
                .append("chargeableWeightKG", chargeableWeightKG).toString();
    }

    public static class ContainerUsageBuilder {

        private static final String ZERO = "0.0000";
        private Order order;
        private Container container;
        private BigDecimal numberAtPOD;
        private BigDecimal numberAtClearingDepot;
        private BigDecimal numberDirectlyToFinalDestination;
        private BigDecimal numberOfDaysAtStorage;

        private BigDecimal volumem3;
        private BigDecimal weightKG;
        private BigDecimal chargeableWeightKG;
        protected BigDecimal cartonsPerContainer;

        public ContainerUsage build() {
            setDefaults();
            return new ContainerUsage(order, container, numberAtPOD, numberAtClearingDepot, numberDirectlyToFinalDestination, numberOfDaysAtStorage,
                    volumem3, weightKG, chargeableWeightKG);
        }

        /*
         * Do any of the default value setting here
         */
        private void setDefaults() {
            /*
             * If a clearing depot is selected on the order, the quantity will default to the quantity specified in the "Number at POD" field for this
             * order. If no clearing depot is selected on the order, the quantity will default to 0.00 and the field will not be editable
             */
            if (order.getShippingInformation() != null && order.getShippingInformation().getClearingDepot() != null) {
                setNumberAtClearingDepot(numberAtPOD);
                setNumberDirectlyToFinalDestination(new BigDecimal(ZERO));
            } else {
                setNumberAtClearingDepot(new BigDecimal(ZERO));
                setNumberDirectlyToFinalDestination(numberAtPOD);
            }
            // TODO - number of days storage from the client config
        }

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

        public ContainerUsageBuilder setContainer(Container container) {
            this.container = container;
            return this;
        }

        public ContainerUsageBuilder setNumberAtPOD(BigDecimal numberAtPOD) {
            this.numberAtPOD = numberAtPOD;
            return this;
        }

        public ContainerUsageBuilder setNumberAtClearingDepot(BigDecimal numberAtClearingDepot) {
            this.numberAtClearingDepot = numberAtClearingDepot;
            return this;
        }

        public ContainerUsageBuilder setNumberDirectlyToFinalDestination(BigDecimal numberDirectlyToFinalDestination) {
            this.numberDirectlyToFinalDestination = numberDirectlyToFinalDestination;
            return this;
        }

        public ContainerUsageBuilder setNumberOfDaysAtStorage(BigDecimal numberOfDaysAtStorage) {
            this.numberOfDaysAtStorage = numberOfDaysAtStorage;
            return this;
        }

        public ContainerUsageBuilder setVolumem3(BigDecimal volumem3) {
            this.volumem3 = volumem3;
            return this;
        }

        public ContainerUsageBuilder setWeightKG(BigDecimal weightKG) {
            this.weightKG = weightKG;
            return this;
        }

        public ContainerUsageBuilder setChargeableWeightKG(BigDecimal chargeableWeightKG) {
            this.chargeableWeightKG = chargeableWeightKG;
            return this;
        }
    }

    public boolean isVolumeOverridden() {
        return volumeOverridden;
    }

    public void setVolumeOverridden(boolean volumeOverridden) {
        this.volumeOverridden = volumeOverridden;
    }

    public boolean isWeightOverridden() {
        return weightOverridden;
    }

    public void setWeightOverridden(boolean weightOverridden) {
        this.weightOverridden = weightOverridden;
    }

    @Override
    public int compareTo(ContainerUsage o) {
        if (getContainer().getContainerType().getName().compareToIgnoreCase(o.getContainer().getContainerType().getName()) == 0) {
            // return getOrder().getNumber().compareTo(o.getOrder().getNumber());
            return getOrder().getAddedToConsignmentDate().compareTo(o.getOrder().getAddedToConsignmentDate());
        }
        return getContainer().getContainerType().getName().compareToIgnoreCase(o.getContainer().getContainerType().getName());
    }

    public BigDecimal getCartonsPerContainer() {
        if (cartonsPerContainer == null) {
            calculateAndSetCartonsPerContainer();
        }
        return cartonsPerContainer;
    }

    public void setCartonsPerContainer(BigDecimal cartonsPerContainer) {
        this.cartonsPerContainer = cartonsPerContainer;
    }

    public void calculateAndSetCartonsPerContainer() {
        if (order != null && !order.inNonEditableState()) {
            final BigDecimal sumPQ = order.getLineItems().stream().map(l -> Optional.ofNullable(l.getPackageQuantity()).orElse(BigDecimal.ZERO))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            setCartonsPerContainer(sumPQ);
        }
    }
}