Shipment.java

package com.tradecloud.domain.shipment;

import com.tradecloud.common.base.HibernateUtils;
import com.tradecloud.common.base.PersistenceBase;
import com.tradecloud.domain.comment.AddedComment;
import com.tradecloud.domain.comment.AddedCommentI;
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.container.PackingList;
import com.tradecloud.domain.container.ShipmentContainer;
import com.tradecloud.domain.costing.Costable;
import com.tradecloud.domain.costing.CostableCostDefinition;
import com.tradecloud.domain.costing.CostableType;
import com.tradecloud.domain.costing.CostingContextType;
import com.tradecloud.domain.costing.clean.ActualShipment;
import com.tradecloud.domain.costing.clean.CostingVisitor;
import com.tradecloud.domain.dms.DocumentGroupState;
import com.tradecloud.domain.dms.DocumentManagementHardCoding;
import com.tradecloud.domain.document.CommercialCreditNote;
import com.tradecloud.domain.document.DeliveryNote;
import com.tradecloud.domain.document.DocumentState;
import com.tradecloud.domain.document.ServiceProviderCreditNote;
import com.tradecloud.domain.document.invoice.CommercialInvoice;
import com.tradecloud.domain.document.invoice.Invoiceable;
import com.tradecloud.domain.document.invoice.ServiceProviderInvoice;
import com.tradecloud.domain.event.Event;
import com.tradecloud.domain.event.ShipmentEvent;
import com.tradecloud.domain.event.ShipmentEventType;
import com.tradecloud.domain.item.LineItem;
import com.tradecloud.domain.model.DMSLinked;
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.model.shipment.ShipmentState;
import com.tradecloud.domain.place.City;
import com.tradecloud.domain.place.PlaceOfDischarge;
import com.tradecloud.domain.place.PlaceOfLoading;
import com.tradecloud.domain.shipment.clearing.CustomsDeclaration;
import com.tradecloud.domain.state.Stateful;
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.*;
import org.hibernate.annotations.ForeignKey;

import javax.persistence.*;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * https://connect.devstream.net/display/Dev/Shipment+Fields.
 */
@Entity
@Table(name = "shipment", uniqueConstraints = {@UniqueConstraint(columnNames = {"number"})})
@Inheritance(strategy = InheritanceType.JOINED)
@Access(AccessType.FIELD)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Shipment")
@XmlSeeAlso({AirShipment.class, SeaShipment.class})
@NamedQueries({
        @NamedQuery(name = "findByIdWithConsignmentsLoaded",
                query = "from Shipment shipment left join fetch shipment.consignments where shipment.id=:id"),
        @NamedQuery(name = "findByIdWithEventsLoaded",
                query = "from Shipment shipment left join fetch shipment.events where shipment.id=:id"),
        @NamedQuery(name = "findByReferenceNotDeleted",
                query = "SELECT shipment FROM Shipment shipment WHERE shipment.reference = :reference AND shipment.state NOT IN ('DELETED')")})
public abstract class Shipment extends PersistenceBase implements Commentable<AddedComment>, Costable,
        Invoiceable, Stateful<ShipmentState, ShipmentEvent>,
        ShipmentInterface, DMSLinked {

    private static final long serialVersionUID = 1L;

    public final static Collection<ShipmentState> NON_EDITABLE_STATES = Arrays.asList(ShipmentState.SIGNED_OFF, ShipmentState.DELETED,
            ShipmentState.STOCK_FULLY_RECEIVED, ShipmentState.STOCK_PARTIALLY_RECEIVED, ShipmentState.COMPLETE);

    public Shipment() {
    }

    public Shipment(String number, String reference) {
        super();
        this.number = number;
        this.reference = reference;
    }

    public Shipment(String number, String reference, Set<Transhipment> transhipments) {
        super();
        this.number = number;
        this.reference = reference;
        this.transhipments = transhipments;
    }

    /**
     * Our internal system assigned shipment number (this is unique in our
     * system).
     */
    @XmlAttribute
    @NotNull(message = "Shipment number is required")
    @Column(nullable = false)
    @Size(max = 255)
    private String number;

    /**
     * Third party or external system reference to this shipment.
     */
    @XmlAttribute
    @Column(nullable = false, unique = true)
    @NotNull(message = "Shipment reference is required")
    @Size(max = 255)
    private String reference;

    @NotNull
    @XmlElement(name = "ShippingInfo")
    @OneToOne(cascade = CascadeType.ALL)
    @ForeignKey(name = "fk_shippingInfo")
    private ShipmentShippingInfo shippingInfo = new ShipmentShippingInfo();

    @XmlAttribute
    @Size(max = 255)
    private String signedOffBy;

    @XmlAttribute
    private BigDecimal grossWeight;

    @XmlAttribute
    private BigDecimal netWeight;

    @XmlAttribute
    private BigDecimal grossVolume;

    @XmlAttribute
    private BigDecimal netVolume;

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

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

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

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

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

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

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

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

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

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

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

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

    @XmlAttribute
    @Column
    private Date originalDocumentsReceivedDate;

    @XmlAttribute
    @Column
    private Date copyDocumentsReceivedDate;

    private Date stateDate;

    @XmlElement(name = "DeparturePlace")
    @ManyToOne(fetch = FetchType.LAZY)
    private PlaceOfLoading departurePlace;

    @XmlElement(name = "ArrivalPlace")
    @ManyToOne(fetch = FetchType.LAZY)
    private PlaceOfDischarge arrivalPlace;

    @XmlElementWrapper(name = "Consignments")
    @XmlElement(name = "Consignment")
    @OrderBy("addedToShipmentDate")
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "shipment", fetch = FetchType.LAZY)
    private Set<Consignment> consignments = new LinkedHashSet<Consignment>();

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    @Fetch(value = FetchMode.SELECT)
    @XmlElementWrapper(name = "Transhipments")
    @XmlElement(name = "Transhipment")
    private Set<Transhipment> transhipments = new HashSet<Transhipment>();

    @XmlElementWrapper(name = "Containers")
    @XmlElement(name = "Container")
    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, orphanRemoval = true, fetch = FetchType.LAZY)
    @Fetch(value = FetchMode.SELECT)
    @OrderBy("created")
    private Set<ShipmentContainer> containers = new LinkedHashSet<ShipmentContainer>();

    @XmlElementWrapper(name = "CommercialInvoices")
    @XmlElement(name = "CommercialInvoice")
    @OneToMany(orphanRemoval = false, fetch = FetchType.LAZY)
    private Set<CommercialInvoice> commercialInvoices = new LinkedHashSet<CommercialInvoice>();

    @XmlElementWrapper(name = "CommercialCreditNotes")
    @XmlElement(name = "CommercialCreditNotes")
    @OneToMany(orphanRemoval = false, fetch = FetchType.LAZY)
    private Set<CommercialCreditNote> commercialCreditNotes = new LinkedHashSet<CommercialCreditNote>();

    @XmlElementWrapper(name = "ServiceProviderCreditNotes")
    @XmlElement(name = "ServiceProviderCreditNotes")
    @OneToMany(orphanRemoval = false, fetch = FetchType.LAZY)
    private Set<ServiceProviderCreditNote> serviceProviderCreditNotes = new LinkedHashSet<ServiceProviderCreditNote>();

    @XmlElementWrapper(name = "ServiceProviderInvoices")
    @XmlElement(name = "ServiceProviderInvoice")
    @OneToMany(orphanRemoval = false, fetch = FetchType.LAZY)
    private Set<ServiceProviderInvoice> serviceProviderInvoices = new LinkedHashSet<ServiceProviderInvoice>();

    private transient String shipmentReportUrl;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @OrderBy("created DESC")
    private Set<CustomsDeclaration> customsDeclarations;

    /**
     * Delivery Notes.
     */
    @XmlElementWrapper(name = "DeliveryNotes")
    @XmlElement(name = "DeliveryNote")
    @OneToMany
    private Set<DeliveryNote> deliveryNotes = new HashSet<DeliveryNote>();

    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionTable(name = "shipment_freetextcomments", joinColumns = {@JoinColumn(name = "shipment_id", unique = false)})
    @Column(name = "reason", unique = true)
    @ForeignKey(name = "fk_shipment")
    @Fetch(value = FetchMode.SELECT)
    @XmlElementWrapper(name = "FreeTextComments")
    @XmlElement(name = "FreeTextComment")
    private List<AddedComment> comments = new ArrayList<AddedComment>();

    /**
     * ShipmentState INITIALISED after object initialisation. (This is an
     * internal state only).
     */
    @NotNull
    @XmlElement
    @Enumerated(EnumType.STRING)
    private ShipmentState state = ShipmentState.FINALISED;

    /**
     * The purchase order events, used to keep track of the history.
     */
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @Fetch(value = FetchMode.SELECT)
    @XmlElementWrapper(name = "ShipmentEvents")
    @XmlElement(name = "ShipmentEvent")
    @OrderBy("createDateTime")
    private List<ShipmentEvent> events = new LinkedList<ShipmentEvent>();

    @OneToOne(cascade = CascadeType.ALL)
    @ForeignKey(name = "fk_packinglist")
    @XmlElement(name = "PackingList")
    private PackingList packingList;

    @OneToOne(cascade = {CascadeType.REMOVE, CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
    @ForeignKey(name = "fk_costablecostdefinition")
    @XmlElement(name = "ShipmentCostableCostDefinition")
    private CostableCostDefinition costableCostDefinition;

    @OneToOne(cascade = {CascadeType.REMOVE, CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
    @ForeignKey(name = "fk_costablecostdefinition")
    @XmlElement(name = "ShipmentCostableCostDefinition")
    private CostableCostDefinition clearingCostableCostDefinition;

    @XmlAttribute
    @Column
    private boolean integrated;

    @XmlAttribute
    @Column
    private Date certificateOfOriginReceivedDate;

    @Enumerated(EnumType.STRING)
    private DocumentGroupState documentGroupStatus;

    @Embedded
    private PlaceAddress consolidationPoint;

    private Date vesselBerthedDate;

    private Date settlementDate;

    @XmlElementWrapper(name = "SubShipments")
    @XmlElement(name = "SubShipment")
    @OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.LAZY)
    @Fetch(value = FetchMode.SELECT)
    @OrderBy("created")
    private Set<SubShipment> subShipments = new LinkedHashSet<SubShipment>();

    private transient Collection<ServiceProviderInvoice> splitInvoices;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinFormula("(SELECT MAX(shipment_shipmentevent.events_id) " +
            "FROM shipment_shipmentevent left join shipmentevent on shipment_shipmentevent.events_id = shipmentevent.id " +
            "WHERE shipment_shipmentevent.shipment_id = id and shipmentevent.eventtype = 'SIGNED_OFF')")
    private ShipmentEvent lastSignedOff;

    @Basic(fetch = FetchType.LAZY)
    @Formula("(select COALESCE(string_agg(distinct(cd.lrnnumber), ', '),string_agg(distinct(lrn.number), ', ')) from shipment s " +
            "left join costsinvoice inv on inv.shipment_id =s.id left join serviceproviderinvoice spi on spi.id= inv.id " +
            "left join lrnnumbers lrn on spi.id = lrn.id left join shipment_customsdeclaration scd on (scd.shipment_id=s.id) " +
            "left join customsdeclaration cd on (cd.id=scd.customsdeclarations_id) where s.id = id)")
    private String lrnNumbers;

    @Basic(fetch = FetchType.LAZY)
    @Formula("(select COALESCE(string_agg(distinct(cd.mrnnumber), ', '),string_agg(distinct(mrn.number), ', ')) from shipment s " +
            "left join costsinvoice inv on inv.shipment_id =s.id left join serviceproviderinvoice spi on spi.id= inv.id " +
            "left join mrnnumbers mrn on spi.id = mrn.id left join shipment_customsdeclaration scd on (scd.shipment_id=s.id) " +
            "left join customsdeclaration cd on (cd.id=scd.customsdeclarations_id) where s.id=id)")
    private String mrnNumbers;

    //actual structure, this does not keep costing
    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE})
    private ActualShipment actualShipment;

    //used in a query
    private Date lastTariffedDate;

    private boolean imports = true;

    @Basic(fetch = FetchType.LAZY)
    @Formula("(select string_agg(cd.mrnnumber, ', ') from shipment_customsdeclaration scd " +
            "left join customsdeclaration cd  on cd.id = scd.customsdeclarations_id " +
            "left join shipment on shipment.id = scd.shipment_id where shipment.id = id)")
    private String clearingMrnNumbers;

    @XmlAttribute
    @Temporal(TemporalType.TIMESTAMP)
    private Date telexReleaseDate;
    private transient  Date clearingInstructionSignedOffDate;

    public ShipmentEvent getLastSignedOff() {
        return this.lastSignedOff;
    }

    @Override
    public PackingList getPackingList() {
        return packingList;
    }

    public void setPackingList(PackingList packingList) {
        this.packingList = packingList;
    }

    @Override
    public Boolean getActive() {
        return state != ShipmentState.DELETED;
    }

    public void addContainer(ShipmentContainer container) {
        container.setShipment(this);
        containers.add(container);
    }

    public void removeContainer(ShipmentContainer container) {
        container.setShipment(null);
        containers.remove(container);
    }

    public void addSubShipment(SubShipment subShipment) {
        subShipment.setShipment(this);
        subShipments.add(subShipment);
    }

    public void removeSubShipment(SubShipment subShipment) {
        subShipment.setShipment(null);
        subShipments.remove(subShipment);
    }

    /**
     * Convenience method that adds the supplied service provider invoice into
     * the member collection of invoices. The shipment reference is set onto
     * this invoice also.
     *
     * @param serviceProviderInvoice The invoice to add to the member collection
     */
    public void addServiceProviderInvoice(ServiceProviderInvoice serviceProviderInvoice) {
        serviceProviderInvoice.setShipment(this);
        serviceProviderInvoices.add(serviceProviderInvoice);
        serviceProviderInvoice.setAddedToShipmentDate(new Date());
    }

    public void removeServiceProviderInvoiceWhenDeleted(ServiceProviderInvoice serviceProviderInvoice) {
        serviceProviderInvoices.remove(serviceProviderInvoice);
    }

    public void removeServiceProviderCreditNoteWhenDeleted(ServiceProviderCreditNote serviceProviderCreditNote) {
        serviceProviderCreditNotes.remove(serviceProviderCreditNote);
    }

    public void removeCommercialInvoiceWhenDeleted(CommercialInvoice commercialInvoice) {
        commercialInvoices.remove(commercialInvoice);
    }

    public void removeCommercialCreditNoteWhenDeleted(CommercialCreditNote commercialCreditNote) {
        commercialCreditNotes.remove(commercialCreditNote);
    }

    public String getNumber() {
        return number;
    }

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

    public String getReference() {
        return reference;
    }

    public void setReference(String reference) {
        this.reference = reference;
    }

    public String getSignedOffBy() {
        return signedOffBy;
    }

    public void setSignedOffBy(String signedOffBy) {
        this.signedOffBy = signedOffBy;
    }

    /**
     * Returns the supply cost currency.
     *
     * @return supply cost currency from the first commercial invoice in the list of commercial invoices.  If no commercial invoices exist then
     * return null
     */
    @Transient
    public Currency getSupplyCostCurrency() {
        if (!commercialInvoices.isEmpty()) {
            return commercialInvoices.iterator().next().getCurrency();
        }
        return null;
    }

    public BigDecimal getGrossWeight() {
        return grossWeight;
    }

    public void setGrossWeight(BigDecimal grossWeight) {
        this.grossWeight = grossWeight;
    }

    public BigDecimal getNetWeight() {
        return netWeight;
    }

    public void setNetWeight(BigDecimal netWeight) {
        this.netWeight = netWeight;
    }

    public BigDecimal getGrossVolume() {
        return grossVolume;
    }

    public void setGrossVolume(BigDecimal grossVolume) {
        this.grossVolume = grossVolume;
    }

    public BigDecimal getNetVolume() {
        return netVolume;
    }

    public void setNetVolume(BigDecimal netVolume) {
        this.netVolume = netVolume;
    }

    public Date getEntryDate() {
        return entryDate;
    }

    public void setEntryDate(Date entryDate) {
        this.entryDate = entryDate;
    }

    public Date getActualDepartureDate() {
        return actualDepartureDate;
    }

    public void setActualDepartureDate(Date actualDepartureDate) {
        this.actualDepartureDate = actualDepartureDate;
    }

    public Date getWarehouseDeliveryDate() {
        return warehouseDeliveryDate;
    }

    public void setWarehouseDeliveryDate(Date warehouseDeliveryDate) {
        this.warehouseDeliveryDate = warehouseDeliveryDate;
    }

    public Date getCarrierReleaseDate() {
        return carrierReleaseDate;
    }

    public void setCarrierReleaseDate(Date carrierReleaseDate) {
        this.carrierReleaseDate = carrierReleaseDate;
    }

    public Date getSignedOffDate() {
        return signedOffDate;
    }

    public void setSignedOffDate(Date signedOffDate) {
        this.signedOffDate = signedOffDate;
    }

    public Date getCostedDate() {
        return costedDate;
    }

    public void setCostedDate(Date costedDate) {
        this.costedDate = costedDate;
    }

    public Date getExFactoryDate() {
        return this.exFactoryDate;
    }

    public void setExFactoryDate(Date exFactoryDate) {
        this.exFactoryDate = exFactoryDate;
    }

    public Date getScheduledDepartureDate() {
        return this.scheduledDepartureDate;
    }

    public void setScheduledDepartureDate(Date scheduledDepartureDate) {
        this.scheduledDepartureDate = scheduledDepartureDate;
    }

    public Date getOriginalDocumentsReceivedDate() {
        return originalDocumentsReceivedDate;
    }

    public void setOriginalDocumentsReceivedDate(Date originalDocumentsReceivedDate) {
        this.originalDocumentsReceivedDate = originalDocumentsReceivedDate;
    }

    public Date getCopyDocumentsReceivedDate() {
        return copyDocumentsReceivedDate;
    }

    public void setCopyDocumentsReceivedDate(Date copyDocumentsReceivedDate) {
        this.copyDocumentsReceivedDate = copyDocumentsReceivedDate;
    }

    public Set<Transhipment> getTranshipments() {
        return transhipments;
    }

    public void setTranshipments(Set<Transhipment> transhipments) {
        this.transhipments = transhipments;
    }

    public Set<Transhipment> updateTranshipments(Set<Transhipment> transhipments) {
        if (this.transhipments == null) {
            this.transhipments = transhipments;
        } else {
            this.transhipments.clear();
            this.transhipments.addAll(transhipments);
        }

        return this.transhipments;
    }

    /**
     * This will return the same result as getActiveCommercialInvoices(),
     * the shipment should not have inactive invoices on it.
     *
     * @return
     */
    @Override
    public Set<CommercialInvoice> getCommercialInvoices() {
        return commercialInvoices;
    }

    @Override
    public Set<CommercialCreditNote> getCommercialCreditNotes() {
        return commercialCreditNotes;
    }

    public Set<ServiceProviderCreditNote> getServiceProviderCreditNotes() {
        return serviceProviderCreditNotes;
    }

    public void setServiceProviderCreditNotes(Set<ServiceProviderCreditNote> serviceProviderCreditNotes) {
        this.serviceProviderCreditNotes = serviceProviderCreditNotes;
    }

    @Override
    public void setCommercialInvoices(Set<CommercialInvoice> commercialInvoices) {
        this.commercialInvoices = commercialInvoices;
    }

    @Override
    public void setCommercialCreditNotes(Set<CommercialCreditNote> commercialCreditNotes) {
        this.commercialCreditNotes = commercialCreditNotes;
    }

    public List<CommercialInvoice> getActiveCommercialInvoices() {
        return PersistenceBase.getActiveList(commercialInvoices);
    }

    public List<CommercialCreditNote> getActiveCommercialCreditNotes() {
        return PersistenceBase.getActiveList(commercialCreditNotes);
    }

    /**
     * This will return the same result as getActiveServiceProviderInvoices(),
     * the shipment should not have inactive invoices on it.
     *
     * @return
     */
    @Override
    public Set<ServiceProviderInvoice> getServiceProviderInvoices() {
        return serviceProviderInvoices;
    }

    @Override
    public void setServiceProviderInvoices(Set<ServiceProviderInvoice> serviceProviderInvoices) {
        this.serviceProviderInvoices = serviceProviderInvoices;
    }

    public List<ServiceProviderInvoice> getActiveServiceProviderInvoices() {
        return PersistenceBase.getActiveList(serviceProviderInvoices);
    }

    public List<ServiceProviderCreditNote> getActiveServiceProviderCreditNotes() {
        return PersistenceBase.getActiveList(serviceProviderCreditNotes);
    }

    public Set<DeliveryNote> getDeliveryNotes() {
        return deliveryNotes;
    }

    public void setDeliveryNotes(Set<DeliveryNote> deliveryNotes) {
        this.deliveryNotes = deliveryNotes;
    }

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

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

    public static long getSerialversionuid() {
        return serialVersionUID;
    }

    public Set<Consignment> getConsignments() {
        return Collections.unmodifiableSet(consignments);
    }

    public void setConsignments(Set<Consignment> consignments) {
        this.consignments = consignments;
    }

    public List<Consignment> getActiveConsignments() {
        return PersistenceBase.getActiveList(consignments);
    }

    public void addConsignment(Consignment consignment) {
        consignments.add(consignment);
        consignment.setShipment(this);
        // To workaround the copy case
        if (consignment.getAddedToShipmentDate() == null) {
            consignment.setAddedToShipmentDate(new Date());
        }
    }

    public void removeConsignment(Consignment consignment) {
        consignment.setShipment(null);
        consignment.setAddedToShipmentDate(null);
        consignments.remove(consignment);
    }

    //use shippinginfo
    @Deprecated
    public PlaceOfLoading getDeparturePlace() {
        return departurePlace;
    }

    //use shippinginfo
    @Deprecated
    public void setDeparturePlace(PlaceOfLoading departurePlace) {
        this.departurePlace = departurePlace;
    }

    //use shippinginfo
    @Deprecated
    public PlaceOfDischarge getArrivalPlace() {
        return arrivalPlace;
    }

    //use shippinginfo
    @Deprecated
    public void setArrivalPlace(PlaceOfDischarge arrivalPlace) {
        this.arrivalPlace = arrivalPlace;
    }

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

    @Override
    public Set<ShipmentContainer> getContainers() {
        return containers;
    }

    public void setContainers(Set<ShipmentContainer> containers) {
        this.containers = containers;
    }

    @Override
    public Date getArrivalDateAtPlaceOfDischarge() {
        return arrivalDateAtPlaceOfDischarge;
    }

    public void setArrivalDateAtPlaceOfDischarge(Date arrivalDateAtPlaceOfDischarge) {
        this.arrivalDateAtPlaceOfDischarge = arrivalDateAtPlaceOfDischarge;
    }

    @Override
    public List<LineItem> getLineItems() {
        List<LineItem> lineItems = new ArrayList<>();
        for (Consignment consignment : consignments) {
            lineItems.addAll(consignment.getLineItems());
        }
        return lineItems;
    }

    @Override
    public List<Order> getOrders() {
        List<Order> orders = new ArrayList<>();
        for (Consignment consignment : consignments) {
            orders.addAll(consignment.getActiveOrders());
        }
        return orders;
    }

    @Override
    public ShipmentState getState() {
        return state;
    }

    @Override
    public void setState(ShipmentState state) {
        this.state = state;
    }

    @Override
    public List<ShipmentEvent> getEvents() {
        return events;
    }

    public void setEvents(List<ShipmentEvent> events) {
        this.events = events;
    }

    @Override
    public ShipmentEvent getLastEvent() {
        return Event.getLastEvent(events);
    }

    @Override
    public Date getEstimatedArrivalDateAtPlaceOfDischarge() {
        return estimatedArrivalDateAtPlaceOfDischarge;
    }

    public void setEstimatedArrivalDateAtPlaceOfDischarge(Date estimatedArrivalDateAtPlaceOfDischarge) {
        this.estimatedArrivalDateAtPlaceOfDischarge = estimatedArrivalDateAtPlaceOfDischarge;
        if (this.originalEstimatedArrivalDateAtPlaceOfDischarge == null && estimatedArrivalDateAtPlaceOfDischarge != null) {
            this.originalEstimatedArrivalDateAtPlaceOfDischarge = estimatedArrivalDateAtPlaceOfDischarge;
        }
    }

    public Date getOriginalEstimatedArrivalDateAtPlaceOfDischarge() {
        return originalEstimatedArrivalDateAtPlaceOfDischarge;
    }

    public void setOriginalEstimatedArrivalDateAtPlaceOfDischarge(Date originalEstimatedArrivalDateAtPlaceOfDischarge) {
        if (this.originalEstimatedArrivalDateAtPlaceOfDischarge == null) {
            this.originalEstimatedArrivalDateAtPlaceOfDischarge = originalEstimatedArrivalDateAtPlaceOfDischarge;
        }
    }

    @Override
    public OrganisationalUnit getOrganisationalUnit() {
        if (consignments.isEmpty()) {
            return null;
        } else {
            for (int i = 0; i < consignments.size(); i++) {
                if (consignments.iterator().next().getOrdersList().size() >= 1) {
                    return consignments.iterator().next().getOrdersList().get(0).getOrganisationalUnit();
                }
            }
        }
        return null;
    }

    @Override
    public void accept(CostingVisitor costingVisitor) {
        for (CommercialInvoice commercialInvoice : getActiveCommercialInvoices()) {
            commercialInvoice.accept(costingVisitor);
        }

        for (CommercialCreditNote commercialCreditNote : getActiveCommercialCreditNotes()) {
            commercialCreditNote.accept(costingVisitor);
        }
        for (ServiceProviderInvoice serviceProviderInvoice : getActiveServiceProviderInvoices()) {
            serviceProviderInvoice.accept(costingVisitor);
        }

        for (ServiceProviderCreditNote serviceProviderCreditNote : getActiveServiceProviderCreditNotes()) {
            serviceProviderCreditNote.accept(costingVisitor);
        }
        costingVisitor.visit(this);
    }

    public void acceptSignedOff(CostingVisitor costingVisitor) {
        for (CommercialInvoice commercialInvoice : getSignedOffCommercialInvoices()) {
            commercialInvoice.accept(costingVisitor);
        }

        for (CommercialCreditNote commercialCreditNote : getSignedOffCommercialCreditNotes()) {
            commercialCreditNote.accept(costingVisitor);
        }
        for (ServiceProviderInvoice serviceProviderInvoice : getSignedOffServiceProviderInvoices()) {
            serviceProviderInvoice.accept(costingVisitor);
        }

        for (ServiceProviderCreditNote serviceProviderCreditNote : getSignedOffServiceProviderCreditNotes()) {
            serviceProviderCreditNote.accept(costingVisitor);
        }
        costingVisitor.visit(this);
    }

    public void addCommercialInvoice(CommercialInvoice commercialInvoice) {
        commercialInvoice.setShipment(this);
        commercialInvoices.add(commercialInvoice);
        commercialInvoice.setAddedToShipmentDate(new Date());
    }

    public void addCommercialCreditNote(CommercialCreditNote commercialCreditNote) {
        commercialCreditNote.setShipment(this);
        commercialCreditNotes.add(commercialCreditNote);
    }

    public void addServiceProviderCreditNote(ServiceProviderCreditNote creditNote) {
        creditNote.setShipment(this);
        serviceProviderCreditNotes.add(creditNote);
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .append(number)
                .append(reference)
                .append(state)
                .toHashCode();
    }

    @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;
        }
        Shipment other = (Shipment) obj;
        return new EqualsBuilder()
                .append(number, other.getNumber())
                .append(reference, other.getReference())
                .append(state, other.getState())
                .isEquals();
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this).append(number).append(reference).append(signedOffBy).append(grossWeight).append(netWeight).append
                        (grossVolume).append(netVolume).append(entryDate).append(warehouseDeliveryDate).append(carrierReleaseDate)
                .append(signedOffDate)
                .append(costedDate).append(shippingInfo).append(departurePlace).append(arrivalPlace).append(actualDepartureDate).
                append(certificateOfOriginReceivedDate).toString();
    }

    public void clearConsignments() {
        consignments.clear();
    }

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

    @Override
    public ShipmentShippingInfo getShippingInfo() {
        return shippingInfo;
    }

    public void setShippingInfo(ShipmentShippingInfo shippingInfo) {
        this.shippingInfo = shippingInfo;
    }

    /**
     * Helper method for the view tier. If the shipment is in any of these states then we will not
     * allow editing of certain fields.
     *
     * @return
     */
    @Deprecated
    public boolean disabled() {
        return inNonEditableState();
    }

    @Override
    public boolean inNonEditableState() {
        return NON_EDITABLE_STATES.contains(state);
    }

    @Override
    public CostableCostDefinition getCostableCostDefinition() {
        return costableCostDefinition;
    }

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

    public CostableCostDefinition getClearingCostableCostDefinition() {
        return clearingCostableCostDefinition;
    }

    public void setClearingCostableCostDefinition(CostableCostDefinition clearingCostableCostDefinition) {
        this.clearingCostableCostDefinition = clearingCostableCostDefinition;
        clearingCostableCostDefinition.setShipment(this);
    }

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

    public String getDMSKey() {
        return getReference();
    }

    public boolean isPacked() {
        return packingList != null && packingList.isPacked();
    }

    public abstract Date getBillOfLadingDate();

    /*
    public boolean match(ActualShipment actualShipment) {
        return this.getReference().equals(actualShipment.getReference()) && this.getNumber().equals(actualShipment.getNumber());
    }
    */

    public boolean isDeleted() {
        return this.getState().equals(ShipmentState.DELETED);
    }

    public boolean isVerified() {
        return this.getState().equals(ShipmentState.VERIFIED);
    }

    public boolean isFinalised() {
        return this.getState().equals(ShipmentState.FINALISED);
    }

    public boolean isSignedOff() {
        return this.getState().equals(ShipmentState.SIGNED_OFF);
    }

    @Override
    public String getCommentsAsString() {
        return AddedCommentI.commentsAsString(getComments());
    }

    @Override
    public CostingContextType getCostingContextType() {
        CostingContextType toReturn = CostingContextType.IMPORT;
        if (!isImports())
            toReturn = CostingContextType.EXPORT;

        Set<Consignment> consignments = getConsignments();
        if (!consignments.isEmpty()) {
            Consignment consignment = consignments.iterator().next();

            toReturn = consignment.getCostingContextType();
        }

        return toReturn;
    }

    public boolean isIntegrated() {
        return integrated;
    }

    public void setIntegrated(boolean integrated) {
        this.integrated = integrated;
    }

    public String getShipmentReportUrl() {
        return shipmentReportUrl;
    }

    public void setShipmentReportUrl(String shipmentReportUrl) {
        this.shipmentReportUrl = shipmentReportUrl;
    }

    public Date getCertificateOfOriginReceivedDate() {
        return certificateOfOriginReceivedDate;
    }

    public void setCertificateOfOriginReceivedDate(Date certificateOfOriginReceivedDate) {
        this.certificateOfOriginReceivedDate = certificateOfOriginReceivedDate;
    }

    public DocumentGroupState getDocumentGroupStatus() {
        return documentGroupStatus;
    }

    public void setDocumentGroupStatus(DocumentGroupState documentGroupStatus) {
        this.documentGroupStatus = documentGroupStatus;
    }

    public PlaceAddress getConsolidationPoint() {
        return consolidationPoint;
    }

    public void setConsolidationPoint(PlaceAddress consolidationPoint) {
        this.consolidationPoint = consolidationPoint;
    }

    @Override
    public boolean hasServiceProviderInvoice() {
        return !this.getActiveServiceProviderInvoices().isEmpty();
    }

    public boolean hasCommercialInvoice() {
        return !this.getActiveCommercialInvoices().isEmpty();
    }

    @Override
    public Date getVesselBerthedDate() {
        return vesselBerthedDate;
    }

    public void setVesselBerthedDate(Date vesselBerthedDate) {
        this.vesselBerthedDate = vesselBerthedDate;
    }

    @Override
    public Set<SubShipment> getSubShipments() {
        return subShipments;
    }

    public void setSubShipments(Set<SubShipment> subShipments) {
        this.subShipments = subShipments;
    }

    @Override
    public Shipment initialize() {
        Shipment initialize = (Shipment) super.initialize();
        HibernateUtils.initializeAndUnproxy(initialize.getServiceProviderInvoices());
        HibernateUtils.initializeAndUnproxy(initialize.getCommercialInvoices());
        HibernateUtils.initializeAndUnproxy(initialize.getCommercialCreditNotes());
        HibernateUtils.initializeAndUnproxy(initialize.getConsignments());
        HibernateUtils.initializeAndUnproxy(initialize.getContainers());
        HibernateUtils.initializeAndUnproxy(initialize.getServiceProviderCreditNotes());
        HibernateUtils.initializeAndUnproxy(initialize.getPackingList());
        HibernateUtils.initializeAndUnproxy(initialize.getSubShipments());
        if (initialize.getPackingList() != null) {
            HibernateUtils.initializeAndUnproxy(initialize.getPackingList().getContainers());
        }
        HibernateUtils.initializeAndUnproxy(initialize.getShippingInfo());
        HibernateUtils.initializeAndUnproxy(initialize.getShippingInfo().getPlaceOfDischarge());
        HibernateUtils.initializeAndUnproxy(initialize.getShippingInfo().getPlaceOfLoading());
        HibernateUtils.initializeAndUnproxy(initialize.getShippingInfo().getFreightForwarder());

        return initialize;
    }

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

    public Date getSettlementDate() {
        return settlementDate;
    }

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

    public Collection<ServiceProviderInvoice> getSplitInvoices() {
        return splitInvoices;
    }

    public void setSplitInvoices(Collection<ServiceProviderInvoice> splitInvoices) {
        this.splitInvoices = splitInvoices;
    }

    public void validateTransitionToCustomReleased() {
        Set<CommercialInvoice> invoices = getCommercialInvoices();
        invoices.stream().forEach(invoice -> {
            if (!(invoice.getState() == DocumentState.PAYMENT_INITIATED ||
                    invoice.getState() == DocumentState.SIGNED_OFF ||
                    invoice.getState() == DocumentState.SETTLED ||
                    invoice.getState() == DocumentState.COMPLETE
            )) {
                throw new IllegalStateException(String.format("Invoice state %s is incorrect to transition" +
                                " to CUSTOMS_RELEASED, Expected invoice states [PAYMENT_INITIATED,SIGNED_OFF,SETTLED,COMPLETE]",
                        invoice.getState(), invoice.getReference()));
            }
        });
    }

    public Set<CustomsDeclaration> getCustomsDeclarations() {
        if (customsDeclarations == null) {
            customsDeclarations = new HashSet();
        }
        return customsDeclarations;
    }

    public Set<CustomsDeclaration> getActiveCustomsDeclarations() {
        return new HashSet<>(PersistenceBase.getActiveList(getCustomsDeclarations()));
    }

    public void setCustomsDeclarations(Set<CustomsDeclaration> customsDeclarations) {
        this.customsDeclarations = customsDeclarations;
    }

    public void addCustomsDeclaration(CustomsDeclaration customsDeclaration) {
        customsDeclaration.setShipment(this);
        getCustomsDeclarations().add(customsDeclaration);
    }

    public Date getStateDate() {
        return stateDate;
    }

    public void setStateDate(Date stateDate) {
        this.stateDate = stateDate;
    }

    public ActualShipment getActualShipment() {
        return actualShipment;
    }

    public void setActualShipment(ActualShipment actualShipment) {
        this.actualShipment = actualShipment;
    }

    public Date getShippedOnBoardDate() {
        return shippedOnBoardDate;
    }

    public void setShippedOnBoardDate(Date shippedOnBoardDate) {
        this.shippedOnBoardDate = shippedOnBoardDate;
    }

    public abstract City getBillPlaceOfIssue();

    public abstract void setBillPlaceOfIssue(City billPlaceOfIssue);

    public String getLrnNumbers() {
        return lrnNumbers;
    }

    public String getMrnNumbers() {
        return mrnNumbers;
    }

    @Override
    public String getDocumentGroupName() {
        switch (getShippingMode()) {
            case AIR:
                return DocumentManagementHardCoding.SHIPMENT_AIR.getDocumentManagementHardCodedName();
            case SEA:
                return DocumentManagementHardCoding.SHIPMENT_SEA.getDocumentManagementHardCodedName();
            default:
                return null;
        }
    }

    public Date getLastTariffedDate() {
        return lastTariffedDate;
    }

    public void setLastTariffedDate(Date lastTariffedDate) {
        this.lastTariffedDate = lastTariffedDate;
    }

    public ShipmentEvent getFirstEvent(ShipmentEventType eventType) {
        return Event.getFirstEvent(events, eventType);
    }

    public List<CommercialInvoice> getSignedOffCommercialInvoices() {
        List<CommercialInvoice> activeCommercialInvoices = getActiveCommercialInvoices();
        if (activeCommercialInvoices != null)
            return activeCommercialInvoices.stream().filter(ci -> ci.isPostSignOffState()).collect(Collectors.toList());
        else return Collections.EMPTY_LIST;
    }

    public List<ServiceProviderInvoice> getSignedOffServiceProviderInvoices() {
        List<ServiceProviderInvoice> serviceProviderInvoices = getActiveServiceProviderInvoices();
        if (serviceProviderInvoices != null)
            return serviceProviderInvoices.stream().filter(ci -> ci.isPostSignOffState()).collect(Collectors.toList());
        else return Collections.EMPTY_LIST;
    }

    public List<CommercialCreditNote> getSignedOffCommercialCreditNotes() {
        List<CommercialCreditNote> activeCommercialCreditNotes = getActiveCommercialCreditNotes();
        if (activeCommercialCreditNotes != null)
            return activeCommercialCreditNotes.stream().filter(ci -> ci.getCommercialInvoice().isPostSignOffState()).collect(Collectors.toList());
        else return Collections.EMPTY_LIST;
    }

    public List<ServiceProviderCreditNote> getSignedOffServiceProviderCreditNotes() {
        List<ServiceProviderCreditNote> activeServiceProviderCreditNotes = getActiveServiceProviderCreditNotes();
        if (activeServiceProviderCreditNotes != null)
            return activeServiceProviderCreditNotes.stream().filter(ci -> ci.getServiceProviderInvoice()
                    .isPostSignOffState()).collect(Collectors.toList());
        else return Collections.EMPTY_LIST;
    }

    public boolean recalculateCosts() {
        return getState() == ShipmentState.FINALISED || getState() == ShipmentState.VERIFIED ||
                getState() == ShipmentState.CUSTOMS_RELEASED;
    }

    public String getClearingMrnNumbers() {
        return clearingMrnNumbers;
    }

    public abstract BigDecimal getBillOfLadingSpotRate();

    public abstract void setBillOfLadingSpotRate(BigDecimal billOfLadingSpotRate);

    public boolean isImports() {
        return imports;
    }

    public void setImports(boolean imports) {
        this.imports = imports;
    }

    public Date getTelexReleaseDate() {
        return telexReleaseDate;
    }

    public Date getClearingInstructionSignedOffDate() {
        return clearingInstructionSignedOffDate;
    }

    public void setClearingInstructionSignedOffDate(Date clearingInstructionSignedOffDate) {
        this.clearingInstructionSignedOffDate = clearingInstructionSignedOffDate;
    }

    public void setTelexReleaseDate(Date telexReleaseDate) {
        this.telexReleaseDate = telexReleaseDate;
    }

}