/*
* Copyright (C) 2014 GG-Net GmbH - Oliver Günther
*
* 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("bestätigt"),
/**
* 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("zurückgezogen"),
/**
* 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 - (Auftragsbestätigung versenden).
*/
SEND_ORDER("Auftragsbestätigung senden", "Dem Kunden die Auftragsbestätigung 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 aushändigen", "Dem Kunden seine Ware aushändigen."),
/**
* Ship Goods.
*/
PREPARE_SHIPPING("Versenden", "Die Ware versenden."),
/**
* Sent Cash on Delivery Contract.
*/
SEND_CASH_ON_DELIVERY_CONTRACT("Nachnahmebedingungen senden", "Dem Kunden die Nachnahmebestätigung zusenden."),
/**
* Wait for the confirmation of the Cash on Delivery Confirmation.
*/
WAIT_FOR_PAYMENT_CONTRACT_CONFIRMATION("Warten auf Bestätigung 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 Bestätigung 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 Bestätigung 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 Bestätigung 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/Geräte 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 zurück 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" : "")
+ "}";
}
}