eu.ggnet.dwoss.redtape.entity.Document.java Source code

Java tutorial

Introduction

Here is the source code for eu.ggnet.dwoss.redtape.entity.Document.java

Source

/*
 * Copyright (C) 2014 GG-Net GmbH - Oliver Gnther
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.ggnet.dwoss.redtape.entity;

import java.io.Serializable;
import java.util.*;

import javax.persistence.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import org.apache.commons.lang3.StringUtils;

import eu.ggnet.dwoss.redtape.entity.util.DocumentEquals;
import eu.ggnet.dwoss.redtape.format.DocumentFormater;
import eu.ggnet.dwoss.rules.*;
import eu.ggnet.dwoss.util.persistence.entity.IdentifiableEntity;

import static eu.ggnet.dwoss.redtape.entity.util.DocumentEquals.Property.*;
import static javax.persistence.CascadeType.*;

/**
 * Represents a Document, like the paper in a real dossier.
 * A Document has a type which represents it function.
 * More about the Types, the allowed workflow and validity are at {@link Type}.
 *
 * @has 1 - n Position
 * @has 1 - n Document.Type
 * @has 1 - n Document.Flag
 * @has 2 - n Address
 *
 * @author bastian.venz, oliver.guenther
 */
@Entity
@NamedQueries({
        @NamedQuery(name = "Document.activeOpenByTypeDirective", query = "select d from Document d where d.active = TRUE and d.closed = FALSE and d.type = ?1 and d.directive = ?2"),
        @NamedQuery(name = "Document.betweenDates", query = "select d from Document d where d.actual between ?1 and ?2 and d.type in (?3) and d.active = true ORDER BY d.identifier ASC"),
        @NamedQuery(name = "Document.findActiveAndOpenByCustomerId", query = "SELECT d FROM Document d WHERE d.dossier.customerId = ?2 AND d.type = ?1 AND d.active = TRUE AND d.closed = FALSE ORDER BY d.dossier.id DESC"),
        @NamedQuery(name = "Document.findActiveByDirective", query = "SELECT d FROM Document d WHERE d.active = TRUE AND d.directive = ?1"),
        @NamedQuery(name = "Document.byIdentifier", query = "SELECT d FROM Document d WHERE d.identifier like ?1 and d.type = ?2 and d.active = true"),
        @NamedQuery(name = "Document.findOpenInvoiceUnpaidByTypePaymentMethod", query = "SELECT d FROM Document d WHERE d.closed = FALSE AND d.active = true AND d.type = ?1 AND d.dossier.paymentMethod = ?2"),
        @NamedQuery(name = "Document.findOpenAnulationByCustomerPaymentMethod", query = "SELECT d FROM Document d WHERE d.closed = FALSE AND d.active = TRUE AND d.dossier.customerId = ?1 AND d.type IN (?2) AND d.dossier.paymentMethod=?3 AND d.directive=?4"),
        @NamedQuery(name = "Document.productIdAndType", query = "SELECT DISTINCT p.document FROM Position p WHERE p.uniqueUnitProductId = ?1 AND p.document.active = TRUE AND p.document.type = ?2 ORDER BY p.document.actual DESC") })
public class Document extends IdentifiableEntity implements Serializable, Comparable<Document> {

    /**
     * A Condition that can be added to a Document. Conditions are meant only to be added.
     * Most of the validation, order and consistency of Conditions is done in RedTape :: Operations in de.dw.redtape.state .
     */
    public enum Condition {

        /**
         * Designated for a customer with Flag.CONFIRM_DOSSIER set and an Order with CASH_ON_DELIVERY,
         * this condition defines, that the customer has confirmed the Document.
         */
        CONFIRMED("besttigt"),
        /**
         * Defines, that the Document is Paid.
         */
        PAID("bezahlt"),
        /**
         * The contents of the Document are picked up.
         */
        PICKED_UP("abgeholt"),
        /**
         * The contents of the Document are sent via UPS.
         */
        SENT("versendet"),
        /**
         * Designated for {@link DocumentType#ANNULATION_INVOICE} and {@link DocumentType#CREDIT_MEMO}.
         * A CreditMemo/Annulation Invoice was has been balanced, meaning the money was paid to the customer.
         */
        REPAYMENT_BALANCED("Storno Rechnung/Gutschrift Zahlung erledigt"),
        /**
         * Designated only for {@link DocumentType#COMPLAINT} which has been rejected, if it is completely unacceptable (e.g., Unit dropped).
         */
        REJECTED("abgelehnt"),
        /**
         * Designated only for {@link DocumentType#COMPLAINT} which has been withdrawn, if the customer itself tells us that everything is ok now.
         */
        WITHDRAWN("zurckgezogen"),
        /**
         * Designated only for {@link DocumentType#COMPLAINT} which has been accepted,
         * if the customer is right and we accept it, moving to a CreditMemo or Annulation Invoice.
         */
        ACCEPTED("angenomen"),
        /**
         * A Document that is closed without completion.
         */
        CANCELED("storniert");

        private final String name;

        private Condition(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    public enum Directive {

        /**
         * Nothing else to do, Dossier is complete.
         */
        NONE("Alles Erledigt", "Der Vorgang ist abgeschlossen, es ist nichts mehr zu tun."),
        /**
         * Send Contract Note - (Auftragsbesttigung versenden).
         */
        SEND_ORDER("Auftragsbesttigung senden",
                "Dem Kunden die Auftragsbesttigung zusenden bzw. den Kunden ber den Auftrag informieren."),
        /**
         * Wait for the Money.
         */
        WAIT_FOR_MONEY("Warten auf Zahlungseingang",
                "Wir warten auf den Kunden, da dieser seine Ware bezahlt. Im Falle von Lastschrift muss diese durch."),
        /**
         * Wait for the Money, reminded.
         */
        WAIT_FOR_MONEY_REMINDED("Warten auf Zahlungseingang. Erinnert!",
                "Wir warten auf den Kunden, da dieser seine Ware bezahlt. Er wurde mindestens einmal erinnert."),
        /**
         * Create an Invoice.
         */
        CREATE_INVOICE("Rechnung erstellen", "Das Dokument Rechnung erstellen und ausdrucken/versenden."),
        /**
         * Deliver/Hand over Goods or Wait for Pick Up.
         */
        HAND_OVER_GOODS("Ware aushndigen", "Dem Kunden seine Ware aushndigen."),
        /**
         * Ship Goods.
         */
        PREPARE_SHIPPING("Versenden", "Die Ware versenden."),
        /**
         * Sent Cash on Delivery Contract.
         */
        SEND_CASH_ON_DELIVERY_CONTRACT("Nachnahmebedingungen senden",
                "Dem Kunden die Nachnahmebesttigung zusenden."),
        /**
         * Wait for the confirmation of the Cash on Delivery Confirmation.
         */
        WAIT_FOR_PAYMENT_CONTRACT_CONFIRMATION("Warten auf Besttigung der Nachnahmebedingungen",
                "Wir warten auf den Kunden, da dieser die Nachnahmebedingungen akzeptiert."),
        /**
         * Wait for the confirmation of the Cash on Delivery Confirmation, reminded.
         */
        WAIT_FOR_PAYMENT_CONTRACT_CONFIRMATION_REMINDED(
                "Warten auf Besttigung der Nachnahmebedingungen. Erinnert!",
                "Wir warten auf den Kunden, da dieser die Nachnamebedingungen akzeptiert. Er wurde mindestens einmal erinnert."),
        /**
         * Wait for the confirmation of Order.
         */
        WAIT_FOR_ORDER_CONFIRMATION("Warten auf Besttigung des Auftrags",
                "Wir warten auf den Kunden, da dieser den Auftrag akzeptiert."),
        /**
         * Wait for the confirmation of Order, reminded.
         */
        WAIT_FOR_ORDER_CONFIRMATION_REMINDED("Warten auf Besttigung des Auftrags. Erinnert!",
                "Wir warten auf den Kunden, da dieser den Auftrag akzeptiert. Er wurde mindestens einmal erinnert."),
        /**
         * Wait for complaint completion.
         */
        WAIT_FOR_COMPLAINT_COMPLETION("Warten auf abschliessende Bearbeitung",
                "Es wird auf eine abschliessende Bearbeitung gewartet, entweder fehlen Informationen/Gerte vom Kunden oder eine Entscheidung im Haus."),
        /**
         * Wait for complaint completion.
         */
        CREATE_CREDIT_MEMO_OR_ANNULATION_INVOICE("Stornorechnung oder Gutschrift erzeugten",
                "Die Reklamation wurde akzeptiert, es muss ein Stornorechung oder eine Gutschrift erzeugt werden."),
        /**
         * Balance the CreditMemo.
         */
        BALANCE_REPAYMENT("Gutschrift/Stornorechnung ausgleichen.",
                "Es wurde eine Gutschrift/Stornorechung erzeugt, diese muss jetzt ausgeglichen werden. Der Kunde muss sein Geld zurck erhalten.");

        private final String name;

        private final String description;

        private Directive(String name, String description) {
            this.name = name;
            this.description = description;
        }

        public String getName() {
            return name;
        }

        public String getDescription() {
            return description;
        }
    }

    /**
     * Flags for the Document.
     * Flags have absolutely nothing in common, but the Set, they are associated to.
     * The Handling and Impact of each Flag differs individually. Each Flag has a unique documentation at which point in
     * the lifecycle of a Dossier it can be set, removed or else.
     */
    public static enum Flag {

        /**
         * This Flag indicates, that at least on Document of the actual Type has been published to the Customers.
         * <p/>
         * Conditions for add:
         * <ul>
         * <li>Customer has been briefed about the document, either printed or send via email</li>
         * </ul>
         * Conditions for removal:
         * <ul>
         * <li>Document Type changed</li>
         * </ul>
         */
        CUSTOMER_BRIEFED,
        /**
         * This Flag indicates, that exactly this Document was published to the Customer.
         * <p/>
         * Conditions for add:
         * <ul>
         * <li>Customer has been briefed about the document, either printed or send via email</li>
         * </ul>
         * Changes in Document or Dossier which indicate a removal:
         * <ul>
         * <li>Document Type</li>
         * <li>A Position</li>
         * <li>Address</li>
         * <li>PaymentMethod</li>
         * <li>Dispatch state</li>
         * </ul>
         */
        CUSTOMER_EXACTLY_BRIEFED
    }

    /**
     * Possible receipts of Payment.
     * Represents the way, the customer balanced the receipt of a Payment.<br />
     * Settlement may have restrictions according to {@link PaymentMethod} of the {@link Document#dossier}
     */
    public enum Settlement {

        /**
         * Direct physical transfer of money.
         * Only possible for:
         * <ul>
         * <li>{@link PaymentMethod#ADVANCE_PAYMENT}</li>
         * <li>{@link PaymentMethod#INVOICE}</li>
         * </ul>
         */
        CASH("Barzahlung"),
        /**
         * Direct electronic transfer of money.
         * Only possible for:
         * <ul>
         * <li>{@link PaymentMethod#ADVANCE_PAYMENT}</li>
         * <li>{@link PaymentMethod#INVOICE}</li>
         * </ul>
         */
        E_CASH("EC-Zahlung"),
        /**
         * Transfer of money provided by the customer.
         * Only possible for:
         * <ul>
         * <li>{@link PaymentMethod#ADVANCE_PAYMENT}</li>
         * <li>{@link PaymentMethod#INVOICE}</li>
         * </ul>
         */
        REMITTANCE("Bank");

        private String name;

        private Settlement(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    @Id
    @GeneratedValue
    private long id;

    @Version
    private short optLock = 0;

    @Enumerated
    private DocumentType type;

    @OneToMany(cascade = ALL, mappedBy = "document", fetch = FetchType.EAGER)
    @OrderBy("id ASC")
    @MapKey(name = "id")
    @Valid
    Map<Integer, Position> positions = new TreeMap<>();

    private boolean active;

    @Valid
    @NotNull // May be removed if UI Validation problem
    @Embedded
    private DocumentHistory history;

    @OneToOne(cascade = { DETACH })
    private Document predecessor;

    @ManyToOne(cascade = { DETACH, MERGE, REFRESH, PERSIST }, optional = false)
    private Dossier dossier;

    @Enumerated
    @ElementCollection(fetch = FetchType.EAGER)
    private Set<Flag> flags = EnumSet.noneOf(Flag.class);

    @ManyToOne(cascade = { DETACH, MERGE, REFRESH, PERSIST }, optional = false)
    private Address invoiceAddress;

    @ManyToOne(cascade = { DETACH, MERGE, REFRESH, PERSIST }, optional = false)
    private Address shippingAddress;

    @ElementCollection(fetch = FetchType.EAGER)
    private Set<Condition> conditions = EnumSet.noneOf(Condition.class);

    @NotNull
    @ElementCollection(fetch = FetchType.EAGER)
    private Set<Settlement> settlements = EnumSet.noneOf(Settlement.class);

    @Enumerated
    @NotNull
    private Directive directive;

    /**
     * Represents this document as closed.
     * Only changes in changesAllowed are still possible.
     */
    private boolean closed;

    /**
     * The identifier, i.e. Invoice.
     */
    private String identifier;

    /**
     * The actual Date of the Document, only the day part is relevant.
     *
     * This Date should be set to the actual value on every new Type of Document.
     */
    @NotNull
    @Temporal(TemporalType.DATE)
    private Date actual;

    public Document() {
        actual = new Date();
    }

    /**
     * Constructor useful for test, has all mandatory parameters.
     *
     * @param type      the type
     * @param directive the directive
     * @param history   the history
     */
    public Document(DocumentType type, Directive directive, DocumentHistory history) {
        this();
        this.type = type;
        this.history = history;
        this.directive = directive;
    }

    /**
     * Returns a partial clone of the Document, without some fields (nearly same goes for {@link Document#equalsContent(Document) }.
     * <p/>
     * The following properties are not cloned:
     * <ul>
     * <li>active</li>
     * <li>dossier</li>
     * <li>history</li>
     * <li>id</li>
     * <li>optLock</li>
     * <li>predecessor</li>
     * </ul>
     * <p/>
     * @return the partial clone
     */
    public Document partialClone() {
        Document clone = new Document();
        clone.setType(type);
        clone.setIdentifier(identifier);
        clone.setActual(actual);
        clone.setInvoiceAddress(invoiceAddress);
        clone.setShippingAddress(shippingAddress);
        clone.setDirective(directive);
        clone.setClosed(closed);
        for (Settlement settlement : settlements)
            clone.add(settlement);
        for (Condition condition : conditions)
            clone.add(condition);
        for (Flag flag : flags)
            clone.add(flag);
        // TODO: I assume a valid Document, meaning there are no holes and no negative values in the position ids and starting from 1.
        for (Integer pos : new TreeSet<>(this.positions.keySet())) {
            clone.append(this.positions.get(pos).partialClone());
        }
        return clone;
    }

    @Override
    public long getId() {
        return id;
    }

    public boolean isActive() {
        return active;
    }

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

    public Date getActual() {
        return actual;
    }

    public void setActual(Date actual) {
        this.actual = actual;
    }

    public Directive getDirective() {
        return directive;
    }

    public void setDirective(Directive directive) {
        this.directive = directive;
    }

    public Dossier getDossier() {
        return dossier;
    }

    public void setDossier(Dossier dossier) {
        if (this.dossier == dossier)
            return; // Implies both null
        if (this.dossier != null)
            this.dossier.documents.remove(this);
        if (dossier != null)
            dossier.documents.add(this);
        this.dossier = dossier;
    }

    public Address getInvoiceAddress() {
        return invoiceAddress;
    }

    public void setInvoiceAddress(Address invoiceAddress) {
        this.invoiceAddress = invoiceAddress;
    }

    public Address getShippingAddress() {
        return shippingAddress;
    }

    public void setShippingAddress(Address shippingAddress) {
        this.shippingAddress = shippingAddress;
    }

    public DocumentType getType() {
        return type;
    }

    public short getOptLock() {
        return optLock;
    }

    public Document setType(DocumentType type) {
        this.type = type;
        return this;
    }

    public String getIdentifier() {
        return identifier;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }

    public DocumentHistory getHistory() {
        return history;
    }

    public void setHistory(DocumentHistory history) {
        this.history = history;
    }

    public Document getPredecessor() {
        return predecessor;
    }

    public void setPredecessor(Document predecessor) {
        this.predecessor = predecessor;
    }

    public boolean isClosed() {
        return closed;
    }

    public void setClosed(boolean closed) {
        this.closed = closed;
    }

    public Document add(Condition condition) {
        if (condition == null)
            return this;
        conditions.add(condition);
        return this;
    }

    public Set<Condition> getConditions() {
        return Collections.unmodifiableSet(conditions);
    }

    public void appendAll(Collection<Position> positionsToAdd) {
        for (Position position : positionsToAdd) {
            append(position);
        }
    }

    public void appendAll(Position... positionsToAdd) {
        if (positionsToAdd != null)
            appendAll(Arrays.asList(positionsToAdd));
    }

    /**
     * Appends a position at the and of the document.
     * <p/>
     * @param position the position to be appended.
     * @return the added position;
     * @throws IllegalArgumentException if the supplied position has a document other than null..
     */
    public Position append(Position position) throws IllegalArgumentException {
        if (position == null)
            return null;
        if (position.document != null)
            throw new IllegalArgumentException("Position has a document other than null: + " + position);
        position.document = this;
        if (positions.keySet().isEmpty())
            position.id = 1;
        else
            position.id = Collections.max(positions.keySet()) + 1;
        positions.put(position.id, position);
        return position;
    }

    /**
     * Appends a position at an exlplizit place, workaround for ReceiptUnitOperation.executeOperation.
     * <p/>
     * @param id
     * @param position the position to be appended.
     * @return the added position;
     * @throws IllegalArgumentException if the supplied position has a document other than null..
     */
    public Position append(int id, Position position) throws IllegalArgumentException {
        if (position == null)
            return null;
        if (position.document != null)
            throw new IllegalArgumentException("Position has a document other than null: + " + position);
        position.document = this;
        position.id = id;
        positions.put(position.id, position);
        return position;
    }

    /**
     * Moves the position on id up, swapping the id with it's predecessor.
     * <p/>
     * @param position the position to be moved.
     * @return true if successful.
     */
    public boolean moveUp(Position position) {
        if (position.id == 1)
            return false;
        positions.remove(position.id);
        Position tmp = positions.remove(position.id - 1);
        tmp.id = position.id;
        position.id -= 1;
        positions.put(position.id, position);
        positions.put(tmp.id, tmp);
        return true;
    }

    /**
     * Moves the position on id down, swapping the id with it's successor.
     * <p/>
     * @param position the position to be moved.
     * @return true if successful.
     */
    public boolean moveDown(Position position) {
        if (position.id == Collections.max(positions.keySet()))
            return false;
        positions.remove(position.id);
        Position tmp = positions.remove(position.id + 1);
        tmp.id = position.id;
        position.id += 1;
        positions.put(position.id, position);
        positions.put(tmp.id, tmp);
        return true;
    }

    /**
     * Clears all Positions.
     * <p/>
     * @return all Positions, which are no longer in the List. (For possible removal)
     */
    public List<Position> removeAllPositions() {
        List<Position> result = new ArrayList<>(positions.values());
        for (Position position : result)
            position.document = null;
        positions.clear();
        return result;
    }

    /**
     * Removes the supplied position, and the reverse mapped document.
     * If the position is null, nothing happens.
     * If the position is not in the map positions, nothing happens.
     * <p/>
     * @param position the position to be removed.
     * @return the position.
     */
    public Position remove(final Position position) {
        if (position == null)
            return null;
        if (!positions.containsValue(position))
            return null;
        return removeAt(position.getId());
    }

    /**
     * Removes if existing a position, which is of Type Unit and has the supplied uniqueUnitId.
     *
     * @param uniqueUnitId the uniqueUnitId
     * @return the remove position or null if none found.
     */
    public Position removeByUniqueUnitId(int uniqueUnitId) {
        for (Position position : new ArrayList<>(positions.values())) {
            if (position.getType() == PositionType.UNIT && position.getUniqueUnitId() == uniqueUnitId) {
                removeAt(position.getId());
                return position;
            }
        }
        return null;
    }

    /**
     * Removes the position at the id.
     * <p/>
     * @param id the id of the position to be removed.
     * @return the removed position.
     */
    public Position removeAt(final int id) {
        if (!positions.containsKey(id))
            return null;
        Position position = positions.remove(id);
        position.document = null;
        position.id = 0;
        // If we have positions which are at the upper end, we want to change there ids.
        if (positions.containsKey(id + 1)) {
            for (int i = (id + 1); i <= Collections.max(positions.keySet()); i++) {
                Position shift = positions.remove(i);
                shift.id = i - 1;
                positions.put(i - 1, shift);
            }
        }
        return position;
    }

    /**
     * Returns all Positions.
     * <p/>
     * @return all Positions.
     */
    public SortedMap<Integer, Position> getPositions() {
        return new TreeMap<>(positions);
    }

    /**
     * Returns all UniqueUnitIds of all Positions of Type Unit.
     *
     * @return all UniqueUnitIds of all Positions of Type Unit, result is never null;
     */
    public Set<Integer> getPositionsUniqueUnitIds() {
        Set<Integer> result = new HashSet<>();
        for (Position position : positions.values()) {
            if (position.getType() == PositionType.UNIT)
                result.add(position.getUniqueUnitId());
        }
        return result;
    }

    /**
     * Returns all Positions with supplied Type.
     *
     * @param type the type
     * @return all Positions with supplied Type.
     */
    public SortedMap<Integer, Position> getPositions(PositionType type) {
        SortedMap<Integer, Position> result = new TreeMap<>();
        for (int pos : positions.keySet()) {
            if (positions.get(pos).getType() == type)
                result.put(pos, positions.get(pos));
        }
        return result;
    }

    /**
     * Returns the position associated with the id.
     * <p/>
     * @param id the id of the position
     * @return the position with the id or null.
     */
    public Position getPosition(int id) {
        return positions.get(id);
    }

    /**
     * Returns a position of type Unit matching the supplied uniqueUnitId, or null if not existent.
     *
     * @param uniqueUnitId the uniqueUnitId
     * @return a position of type Unit matching the supplied uniqueUnitId, or null if not existent.
     */
    public Position getPositionByUniqueUnitId(int uniqueUnitId) {
        for (Position position : positions.values()) {
            if (position.getType() == PositionType.UNIT && position.getUniqueUnitId() == uniqueUnitId)
                return position;
        }
        return null;
    }

    public Set<Flag> getFlags() {
        return new HashSet<>(flags);
    }

    public void add(Flag flag) {
        this.flags.add(flag);
    }

    public void remove(Flag flag) {
        this.flags.remove(flag);
    }

    public Set<Settlement> getSettlements() {
        return Collections.unmodifiableSet(settlements);
    }

    public void add(Settlement settlement) {
        this.settlements.add(settlement);
    }

    public void remove(Settlement settlement) {
        this.settlements.remove(settlement);
    }

    public double getPrice() {
        double price = 0.;
        for (Position position : positions.values()) {
            price += (position.getAmount() * position.getPrice());
        }
        return price;
    }

    public double getAfterTaxPrice() {
        double afterTax = 0.;
        for (Position position : positions.values()) {
            afterTax += (position.getAmount() * position.getAfterTaxPrice());
        }
        return afterTax;
    }

    /**
     * Returns true if and only if at least one Position is from a given Type.
     * <p/>
     * @param type The Type
     * @return true if at least one Position is from a given Type.
     */
    public boolean containsPositionType(PositionType type) {
        for (Position position : positions.values()) {
            if (position.getType() == type)
                return true;
        }
        return false;
    }

    /**
     * Returns true if any of the condition is at the document.
     * <p>
     * @param filter the condition to test against.
     * @return true if any of the condition is at the document.
     */
    public boolean containsAny(Condition... filter) {
        return !containsNone(filter);
    }

    /**
     * Returns true if none of the condition is at the document.
     * <p>
     * @param filter the condition to test against.
     * @return true if none of the condition is at the document.
     */
    public boolean containsNone(Condition... filter) {
        if (filter == null || filter.length == 0)
            throw new RuntimeException("The filter for contains any must not be null or empty");
        Set<Condition> toRetain = new HashSet<>(conditions);
        toRetain.retainAll(Arrays.asList(filter));
        return toRetain.isEmpty();
    }

    /**
     * Equals the content of the Document, not evaluating all parameters (nearly same goes for {@link Document#partialClone() }.
     *
     * The following parameters are ignored:
     * <ul>
     * <li>id</li>
     * <li>optLock</li>
     * <li>active</li>
     * <li>history</li>
     * <li>predecessor : Should be impossible, that it changes and create some exception</li>
     * </ul>
     *
     * @param other the other Document
     * @return true if content is equal, otherwise false.
     */
    public boolean equalsContent(Document other) {
        return new DocumentEquals().ignore(ID, ACTIVE, HISTORY, PREDECESSOR).equals(this, other);
    }

    /**
     * Verifies if the difference between this and other have no impact on {@link Flag#CUSTOMER_EXACTLY_BRIEFED}
     *
     * @param other the other document to difference against.
     * @return true if the difference has no impact.
     */
    public boolean isStillExactlyBriefed(Document other) {
        if (other == null)
            throw new NullPointerException("The other Document must not be null");
        if (this.getDossier().isDispatch() != other.getDossier().isDispatch())
            return false;
        if (this.getDossier().getPaymentMethod() != other.getDossier().getPaymentMethod())
            return false;
        if (this.type != other.type)
            return false;
        if (!Objects.equals(this.invoiceAddress, other.invoiceAddress))
            return false;
        if (!Objects.equals(this.shippingAddress, other.shippingAddress))
            return false;
        if (this.positions.size() != other.positions.size())
            return false;
        Iterator<Position> p1 = new TreeSet<>(this.positions.values()).iterator();
        Iterator<Position> p2 = new TreeSet<>(other.positions.values()).iterator();
        while (p1.hasNext()) {
            Position p1p = p1.next();
            Position p2p = p2.next();
            if (!p1p.equalsContent(p2p))
                return false;
        }
        return true;
    }

    @Override
    public int compareTo(Document o) {
        if (o == null)
            return -1;
        if (this.type != o.type)
            return this.type.compareTo(o.type);
        return this.hashCode() - o.hashCode();
    }

    public String toTypeConditions() {
        return (StringUtils.isBlank(identifier) ? "id=" + id : identifier) + ", " + type.getName() + ", "
                + DocumentFormater.toConditions(this);
    }

    @Override
    public String toString() {
        return "Document{" + "id=" + id + ", type=" + type + ", closed=" + closed + ",actual=" + actual
                + ", conditions=" + conditions + ", directive=" + directive + ", positions=" + positions
                + "settlements=" + settlements + ", active=" + active + ", history=" + history + ", predecessor.id="
                + (predecessor == null ? null : predecessor.getId()) + ", dossier.id="
                + (dossier == null ? null : dossier.getId()) + ", flags=" + flags + ", invoiceAddress="
                + invoiceAddress + ", shippingAddress=" + shippingAddress + ", identifier=" + identifier + '}';
    }

    public String toSimpleLine() {
        return this.getClass().getSimpleName() + "{" + "id=" + id
                + (identifier == null ? "" : ",idenifier=" + identifier) + ",type=" + type
                + (active ? ",active" : "") + (closed ? ",closed" : "") + "}";
    }

}