eu.ggnet.dwoss.report.entity.ReportLine.java Source code

Java tutorial

Introduction

Here is the source code for eu.ggnet.dwoss.report.entity.ReportLine.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.report.entity;

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

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

import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

import org.apache.commons.lang3.StringUtils;
import org.metawidget.inspector.annotation.*;

import eu.ggnet.dwoss.rules.*;
import eu.ggnet.dwoss.util.persistence.EagerAble;
import eu.ggnet.dwoss.util.persistence.entity.IdentifiableEntity;

import lombok.*;

import static eu.ggnet.dwoss.rules.DocumentType.*;
import static eu.ggnet.dwoss.rules.PositionType.*;

/**
 * This is a Line of a report. It could be represent any type of Position.
 * <p>
 * This is the documentation how a Report is filled with ReportLines:
 * TODO: Rewrite
 * <ol>
 * <li>Sammle alle Complaint Positionen raus die mit Repayment Positionen in vorhanden Reporten existieren  zu den allten Reporten</li>
 * <li>Sammle alle only Invoice Positions raus  Report</li>
 * <li>Sammle alle Repayment Positions raus  Report</li>
 * <li>Sammle alle Complaint Positionen die mit den Repayment Positionen zusammenhngen raus  Report</li>
 * <li>Sammle alle Compleints die DISCHARDED sind  Report</li>
 * <li>Alles was brig ist, sollten (offene) Complaints sein  Active Info</li>
 * </ol>
 * This is the documentation how a Report is shown in the ReportViews:
 * Expects two sets of data: Report Data and Active Info
 * Has 4 views: Report View, Repayment View, Report Info, Active Info
 * <ol>
 * <li>Invoice only  Report Data</li>
 * <li>Repayment Unit + Invoice  Report Info</li>
 * <li>Repayment Unit_Annex + Invoice  Report Info + Repayment Data oder zusammenfassen mit info in ReportData</li>
 * <li>Repayment only  Repayment Data</li>
 * <li>Complaint Discharched + Invoice  Report Data + Report Info</li>
 * <li>Complaint Discharched Only  Report Info</li>
 * </ol>
 * <p>
 * @author bastian.venz, oliver.guenther
 */
@Entity
@NamedQueries({
        @NamedQuery(name = "ReportLine.allReverse", query = "SELECT r FROM ReportLine r ORDER BY r.reportingDate DESC, r.refurbishId ASC"),
        @NamedQuery(name = "ReportLine.byUniqueUnitId", query = "SELECT r FROM ReportLine r WHERE r.uniqueUnitId = ?1"),
        @NamedQuery(name = "ReportLine.byProductIdMissingContractorPartNo", query = "SELECT r FROM ReportLine r WHERE r.productId = ?1 and r.contractor = ?2 and r.contractorPartNo is null"),
        @NamedQuery(name = "ReportLine.byRefurbishId", query = "SELECT r FROM ReportLine r WHERE r.refurbishId = ?1"),
        @NamedQuery(name = "ReportLine.bySerialAndPositionTypeAndDossierId", query = "SELECT r FROM ReportLine r WHERE r.serial = ?1 AND r.positionType = ?2 AND r.dossierId = ?3"),
        @NamedQuery(name = "ReportLine.lastReported", query = "SELECT MAX(r.reportingDate) FROM ReportLine r"),
        @NamedQuery(name = "ReportLine.betweenDates", query = "SELECT r FROM ReportLine r WHERE r.reportingDate BETWEEN ?1 AND ?2"),
        @NamedQuery(name = "ReportLine.unreported", query = "SELECT r FROM ReportLine r WHERE r.reportingDate >= :from AND r.reportingDate <= :till AND r.id NOT IN (SELECT pl.id FROM Report p JOIN p.lines pl WHERE p.type = :type)"),
        @NamedQuery(name = "ReportLine.unreportedbyContractors", query = "SELECT r FROM ReportLine r WHERE r.reportingDate >= :from AND r.reportingDate <= :till AND r.contractor IN (:contractors) AND r.id NOT IN (SELECT pl.id FROM Report p JOIN p.lines pl WHERE p.type = :type)"),
        @NamedQuery(name = "ReportLine.unreportedbyPositionTypes", query = "SELECT r FROM ReportLine r WHERE r.reportingDate >= :from AND r.reportingDate <= :till AND r.positionType IN (:positionTypes) AND r.id NOT IN (SELECT pl.id FROM Report p JOIN p.lines pl WHERE p.type = :type)"),
        @NamedQuery(name = "ReportLine.unreportedbyContractorsPositionTypes", query = "SELECT r FROM ReportLine r WHERE r.reportingDate >= :from AND r.reportingDate <= :till AND r.contractor IN (:contractors) AND r.positionType IN (:positionTypes) AND r.id NOT IN (SELECT pl.id FROM Report p JOIN p.lines pl WHERE p.type = :type)"),
        @NamedQuery(name = "ReportLine.revenueByPositionTypesAndDateReported", query = "SELECT new eu.ggnet.dwoss.report.eao.RevenueHolder(rl.reportingDate, rl.documentType, rl.salesChannel, rl.contractor, sum(rl.price), sum(rl.purchasePrice))"
                + " FROM ReportLine rl WHERE rl.positionType in(:positions) and rl.reportingDate >= :start and rl.reportingDate <= :end and rl.documentType in(1,3) "
                + " and rl.purchasePrice != 0 GROUP BY rl.reportingDate, rl.documentType, rl.salesChannel, rl.contractor"),
        @NamedQuery(name = "ReportLine.revenueByPositionTypesAndDate", query = "SELECT new eu.ggnet.dwoss.report.eao.RevenueHolder(rl.reportingDate, rl.documentType, rl.salesChannel, rl.contractor, sum(rl.price), 0.)"
                + " FROM ReportLine rl WHERE rl.positionType in(:positions) and rl.reportingDate >= :start"
                + " and rl.reportingDate <= :end and rl.documentType in(1,3) GROUP BY rl.reportingDate, rl.documentType, rl.salesChannel, rl.contractor")

})
@NoArgsConstructor
public class ReportLine extends IdentifiableEntity implements Serializable, EagerAble, Comparable<ReportLine> {

    /**
     * Types for single line references.
     */
    public static enum SingleReferenceType {

        /**
         * Represents a reference between a Line of type UNIT and WARRANTY in the same Document.
         */
        WARRANTY

    }

    @Value
    public static class Storeable implements Serializable {

        private final long id;

        private final double marginPercentage;

        private final double purchasePrice;

    }

    /**
     * Helper Class to ensure no StackOverflows on fetchEager.
     */
    static class EagerHelper {

        private final Set<Report> reports = new HashSet<>();

        private final Set<ReportLine> lines = new HashSet<>();

        public void fetch(ReportLine line) {
            if (lines.contains(line))
                return;
            lines.add(line);
            for (ReportLine reference : line.refrences) {
                fetch(reference);
            }
            for (Report report : line.reports) {
                fetch(report);
            }
        }

        public void fetch(Report report) {
            if (reports.contains(report))
                return;
            reports.add(report);
            for (ReportLine reportLine : report.getLines()) {
                fetch(reportLine);
            }
        }
    }

    /**
     * The WorkflowStatus represents the status of a ReportedLine.
     * A Status other then Default assumes, that a Document can be reported twice. For now this is the Case for the {@link DocumentType#COMPLAINT}.
     * <ul>
     * <li>UNDER_PROGRESS is a Line if a Complaint is created.</li>
     * <li>CHARGED is a Line if a Complaint has the condition ACCEPTED.</li>
     * <li>DISCHARGED is a Line if a Complaint has one of the conditions WITHDRAWN or REJECTED.</li>
     * </ul>
     * <p>
     */
    @AllArgsConstructor
    @Getter
    public static enum WorkflowStatus {

        /**
         * Default status, no other Line is assume of the same Document.
         */
        DEFAULT(""),
        /**
         * (External) Workflow is in a state, that it will create another line.
         * (e.g. A Complaint is created)
         */
        UNDER_PROGRESS("(o)"),
        /**
         * (External) Workflow is in a end state, that results in some form of continuance.
         * (e.g. Complaint is accepted, so a Credit Memo will be created)
         */
        CHARGED("(+)"),
        /**
         * (External) Workflow is in a end state, that does not contiune.
         * (e.g. Complaint is reject, so nothing happens)
         */
        DISCHARGED("(-)");

        /**
         * A simple sign reprensentation of the Workflow (o), (-) or (+).
         */
        private final String sign;

    }

    public static Set<ReportLine.Storeable> toStorables(Collection<ReportLine> lines) {
        Set<ReportLine.Storeable> result = new HashSet<>();
        for (ReportLine rl : lines) {
            result.add(rl.toStorable());
        }
        return result;
    }

    @Id
    @Getter
    @GeneratedValue
    private long id;

    /**
     * Integer value for optimistic locking.
     */
    @Version
    @UiHidden
    private int optLock;

    @ManyToMany(cascade = { CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH }, fetch = FetchType.EAGER)
    private final Set<ReportLine> refrences = new HashSet<>();

    /**
     * The date when this {@link ReportLine} was reported.
     */
    @Temporal(javax.persistence.TemporalType.DATE)
    @Getter
    @Setter
    private Date reportingDate;

    /**
     * The dossier id of the {@link ReportLine} where the referendet position exist.
     */
    @NotNull
    @Getter
    @Setter
    private long dossierId;

    /**
     * The Identifier of the referended Dossier.
     */
    @Getter
    @Setter
    private String dossierIdentifier;

    /**
     * The id of the document where the refended position of this {@link ReportLine} exist.
     */
    @NotNull
    @Getter
    @Setter
    private long documentId;

    /**
     * The Identifier of the referended Document.
     */
    @Getter
    @Setter
    private String documentIdentifier;

    /**
     * The type of the document that referend the {@link ReportLine}.
     */
    @NotNull
    @Getter
    private DocumentType documentType;

    @Getter
    @UiHidden
    private String documentTypeName;

    /**
     * The date when the newest Document was created.
     */
    @Temporal(javax.persistence.TemporalType.DATE)
    @Getter
    @Setter
    private Date actual;

    @Getter
    @Setter
    @NotNull
    private WorkflowStatus workflowStatus = WorkflowStatus.DEFAULT;

    //PositionData
    @Getter
    @Setter
    @UiSection("Position")
    private double amount;

    /**
     * Name of the RedTape Position.
     */
    @NotNull
    @Getter
    @Setter
    private String name;

    /**
     * The description of the Position.
     */
    @Lob
    @Getter
    @Setter
    @UiLarge
    @Column(length = 65536)
    private String description;

    /**
     * The type of the position that referend the {@link ReportLine}.
     */
    @NotNull
    @Getter
    private PositionType positionType;

    /**
     * The type of the position that referend the {@link ReportLine}.
     */
    @Getter
    @UiHidden
    private String positionTypeName;

    /**
     * The price without tax of this position, normally the sales price of a unit.
     */
    @Getter
    private double price;

    @Transient
    private transient DoubleProperty priceProperty;

    @Getter
    @Setter
    private double tax;

    /**
     * The price with tax of this position, normally the sales price of a unit.
     */
    @Getter
    private double afterTaxPrice;

    @Transient
    private transient DoubleProperty afterTaxPriceProperty;

    @Getter
    @Setter
    private int bookingAccount;

    /**
     * The id of the Customer wiche has the dossier.
     */
    @Min(0)
    @Getter
    @Setter
    @UiSection("Customer")
    private long customerId;

    /**
     * This String contains a escaped String with the Invoice Adress.
     */
    @Getter
    @Setter
    @Size(max = 255)
    private String customerName;

    @Getter
    @Setter
    @Size(max = 255)
    private String customerCompany;

    @Lob
    @Getter
    @Setter
    @UiLarge
    @Column(length = 65536)
    private String invoiceAddress;

    // -- Product
    /**
     * This is a referenz to the ProductGroup of the Product, if this ReportLine represent a Unit oder Product.
     */
    @Getter
    @UiSection("Product")
    private ProductGroup productGroup;

    /**
     * This is a String representation of the ProductGroup.
     * This String exist because the possibility that a Product Group will change his name or will be deleted.
     */
    @Getter
    @UiHidden
    private String productGroupName;

    /**
     * This is a referenz to the ProductBrand of the Product, if this ReportLine represent a Unit oder Product.
     */
    @Getter
    private TradeName productBrand;

    /**
     * This is a String representation of the Product Brand.
     * This String exist because the possibility that a Product Brand will change his name or will be deleted.
     */
    @Getter
    @UiHidden
    private String productBrandName;

    /**
     * This is a String representation of the Product Name.
     */
    @Setter
    @Getter
    private String productName;

    /**
     * The part no of the product.
     */
    @Getter
    @Setter
    private String partNo;

    /**
     * This double is the manufacturer cost price.
     * It is possible and very common that we have access to this, AFTER we created the Report.
     */
    @Getter
    private double manufacturerCostPrice;

    @Transient
    private transient DoubleProperty manufacturerCostPriceProperty;

    /**
     * The id of the product. If the id is 0, than there is no Product.
     */
    @Getter
    @Setter
    private long productId;

    // -- Unit
    /**
     * The refurbish id of the {@link ReportLine}, when it exist.
     */
    @Getter
    @Setter
    @UiSection("Unit")
    private String refurbishId;

    /**
     * The serial number of the unit.
     */
    @Getter
    @Setter
    private String serial;

    /**
     * The manufacture date.
     */
    @Temporal(javax.persistence.TemporalType.DATE)
    @Getter
    @Setter
    private Date mfgDate;

    /**
     * The id of the unique unit.
     */
    @Getter
    @Setter
    private long uniqueUnitId;

    /**
     * The percentage of the marge of this Line.
     */
    @Getter
    private double marginPercentage;

    @Transient
    private transient DoubleProperty marginPercentageProperty;

    /**
     * The Price with this unit was buyed.
     */
    @Getter
    private double purchasePrice;

    @Transient
    private transient DoubleProperty purchasePriceProperty;

    @Getter
    private SalesChannel salesChannel;

    @Getter
    @UiHidden
    private String salesChannelName;

    /**
     * This is a referenz to the contractor of the Product, if this ReportLine represent a Unit oder Product.
     */
    @Getter
    @UiSection("Contractor")
    private TradeName contractor;

    /**
     * This is a String representation of the contractor name.
     * This String exist because the possibility that a contractor name will change his name or will be deleted.
     */
    @Getter
    @UiHidden
    private String contractorName;

    /**
     * This String is a Partnumber that we will be given from the specific contractor.
     * It is possible and very common that we have access to this, AFTER we created the Report.
     */
    @Getter
    @Setter
    private String contractorPartNo;

    /**
     * This double is the reference price for the specific contractor.
     * It is possible and very common that we have access to this, AFTER we created the Report.
     */
    @Getter
    private double contractorReferencePrice;

    @Transient
    private transient DoubleProperty contractorReferencePriceProperty;

    @Getter
    @Setter
    private String customerEmail;

    /**
     * This set contains the ReportsLines of the Reports, where is the mapping unidirectional.
     */
    @ManyToMany(mappedBy = "lines")
    @UiHidden
    Set<Report> reports = new HashSet<>();

    @NotNull
    @ManyToMany
    @MapKeyEnumerated
    @JoinTable(name = "ReportLine_SingleReferences")
    private final Map<SingleReferenceType, ReportLine> singleReferences = new HashMap<>();

    // Change to Add. This is past.
    @Transient
    private transient BooleanProperty addedToReportProperty;

    @Getter
    @Setter
    @Column(length = 65536)
    @Lob
    @UiLarge
    private String comment;

    @Builder
    @SuppressWarnings("OverridableMethodCallInConstructor")
    public ReportLine(String name, String description, long dossierId, String dossierIdentifier, long documentId,
            String documentIdentifier, PositionType positionType, DocumentType documentType, long customerId,
            double amount, double tax, double price, double afterTaxPrice, int bookingAccount,
            String invoiceAddress, String refurbishId, long uniqueUnitId, String serial, Date mfgDate,
            long productId, String partNo, String customerEmail) {
        this.name = name;
        this.description = description;
        this.dossierId = dossierId;
        this.dossierIdentifier = dossierIdentifier;
        this.documentId = documentId;
        this.documentIdentifier = documentIdentifier;
        this.setPositionType(positionType);
        this.setDocumentType(documentType);
        this.customerId = customerId;
        this.amount = amount;
        this.tax = tax;
        this.price = price;
        this.afterTaxPrice = afterTaxPrice;
        this.bookingAccount = bookingAccount;
        this.invoiceAddress = invoiceAddress;
        this.refurbishId = refurbishId;
        this.uniqueUnitId = uniqueUnitId;
        this.serial = serial;
        this.mfgDate = mfgDate;
        this.productId = productId;
        this.partNo = partNo;
        this.customerEmail = customerEmail;
    }

    public ReportLine getReference(SingleReferenceType type) {
        return singleReferences.get(type);
    }

    public void setReference(SingleReferenceType type, ReportLine line) {
        if (line == null)
            return;
        singleReferences.put(type, line);
        line.singleReferences.put(type, this);
    }

    public void setPrice(double price) {
        if (priceProperty != null)
            priceProperty.set(price);
        else
            this.price = price;
    }

    public DoubleProperty priceProperty() {
        if (priceProperty == null) {
            priceProperty = new SimpleDoubleProperty(price);
            priceProperty.addListener(new ChangeListener<Number>() {

                @Override
                public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
                    price = newValue.doubleValue();
                }
            });
        }
        return priceProperty;
    }

    public void setAfterTaxPrice(double price) {
        if (afterTaxPriceProperty != null)
            afterTaxPriceProperty.set(price);
        else
            this.afterTaxPrice = price;
    }

    public DoubleProperty afterTaxPriceProperty() {
        if (afterTaxPriceProperty == null) {
            afterTaxPriceProperty = new SimpleDoubleProperty(afterTaxPrice);
            afterTaxPriceProperty.addListener(new ChangeListener<Number>() {

                @Override
                public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
                    afterTaxPrice = newValue.doubleValue();
                }
            });
        }
        return afterTaxPriceProperty;
    }

    public void setManufacturerCostPrice(double value) {
        if (manufacturerCostPriceProperty != null)
            manufacturerCostPriceProperty.set(value);
        else
            this.manufacturerCostPrice = value;
    }

    public DoubleProperty manufacturerCostPriceProperty() {
        if (manufacturerCostPriceProperty == null) {
            manufacturerCostPriceProperty = new SimpleDoubleProperty(manufacturerCostPrice);
            manufacturerCostPriceProperty.addListener((ov, o, n) -> manufacturerCostPrice = n.doubleValue());
        }
        return manufacturerCostPriceProperty;
    }

    public void setMarginPercentage(double value) {
        if (marginPercentageProperty != null)
            marginPercentageProperty.set(value);
        else
            this.marginPercentage = value;
    }

    public DoubleProperty marginPercentageProperty() {
        if (marginPercentageProperty == null) {
            marginPercentageProperty = new SimpleDoubleProperty(marginPercentage);
            marginPercentageProperty.addListener(new ChangeListener<Number>() {

                @Override
                public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
                    marginPercentage = newValue.doubleValue();
                }
            });
        }
        return marginPercentageProperty;
    }

    public void setPurchasePrice(double value) {
        if (purchasePriceProperty != null)
            purchasePriceProperty.set(value);
        else
            this.purchasePrice = value;
    }

    public DoubleProperty purchasePriceProperty() {
        if (purchasePriceProperty == null) {
            purchasePriceProperty = new SimpleDoubleProperty(purchasePrice);
            purchasePriceProperty.addListener(new ChangeListener<Number>() {

                @Override
                public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
                    purchasePrice = newValue.doubleValue();
                }
            });
        }
        return purchasePriceProperty;
    }

    public void setContractorReferencePrice(double value) {
        if (contractorReferencePriceProperty != null)
            contractorReferencePriceProperty.set(value);
        else
            this.contractorReferencePrice = value;
    }

    public DoubleProperty contractorReferencePriceProperty() {
        if (contractorReferencePriceProperty == null) {
            contractorReferencePriceProperty = new SimpleDoubleProperty(contractorReferencePrice);
            contractorReferencePriceProperty.addListener(new ChangeListener<Number>() {

                @Override
                public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
                    contractorReferencePrice = newValue.doubleValue();
                }
            });
        }
        return contractorReferencePriceProperty;
    }

    /**
     * This setter Method sets the productGroup and the productGroupName.
     * <p/>
     * @param productGroup
     */
    public void setProductGroup(ProductGroup productGroup) {
        this.productGroup = productGroup;
        if (productBrand != null) {
            this.productGroupName = productGroup.getNote();
        } else
            this.productGroupName = null;
    }

    /** This setter Method sets the productBrand and the productBrandName.
     * <p/>
     * @param productBrand
     */
    public void setProductBrand(TradeName productBrand) {
        this.productBrand = productBrand;
        if (productBrand != null) {
            this.productBrandName = productBrand.getName();
        } else
            this.productBrandName = null;
    }

    /**
     * This setter Method sets the contractor and the contractorName.
     * <p/>
     * @param contractor
     */
    public void setContractor(TradeName contractor) {
        this.contractor = contractor;
        if (contractor != null) {
            this.contractorName = contractor.getName();
        } else
            this.contractorName = null;
    }

    /**
     * This setter Method sets the salesChannel and the salesChannelName.
     * <p/>
     * @param salesChannel
     */
    public void setSalesChannel(SalesChannel salesChannel) {
        this.salesChannel = salesChannel;
        if (salesChannel != null) {
            this.salesChannelName = salesChannel.getName();
        } else
            this.salesChannelName = null;
    }

    public void setDocumentType(DocumentType documentType) {
        this.documentType = documentType;
        this.documentTypeName = documentType.getName();
    }

    public void setPositionType(PositionType positionType) {
        this.positionType = positionType;
        this.positionTypeName = positionType.getName();
    }

    public void addAll(Collection<ReportLine> lines) {
        if (lines == null || lines.isEmpty())
            return;
        for (ReportLine reportLine : lines) {
            add(reportLine);
        }
    }

    /**
     * Returns all references in a new collection.
     * <p>
     * @return all references in a new collection.
     */
    public NavigableSet<ReportLine> getRefrences() {
        return new TreeSet<>(refrences);
    }

    /**
     * Calls {@link Collection#retainAll(java.util.Collection) } on a new instance of references and returns the resulting collection.
     * <p>
     * @param other the other collection to use as parameter on retainAll.
     * @return the resulting collection
     */
    public NavigableSet<ReportLine> retainReferences(Collection<ReportLine> other) {
        NavigableSet<ReportLine> result = getRefrences();
        result.retainAll(other);
        return result;
    }

    /**
     * This add a ReportLine of the references Set. This is a bidirectional mapping method.
     * <p/>
     * @param reportLine
     */
    public void add(ReportLine reportLine) {
        if (reportLine == null)
            return;
        if (this.equals(reportLine))
            return; // Don't ref oureself.
        refrences.add(reportLine);
        reportLine.refrences.add(this);
    }

    /**
     * This removes a ReportLine of the references Set. This is a bidirectional mapping method.
     * <p/>
     * @param reportLine
     */
    public void remove(ReportLine reportLine) {
        if (reportLine == null)
            return;
        refrences.remove(reportLine);
        reportLine.refrences.remove(this);
    }

    /**
     * Returs a unmodifiable Set of assosiated reports.
     * <p>
     * @return a unmodifiable Set of assosiated reports.
     */
    public Set<Report> getReports() {
        return Collections.unmodifiableSet(reports);
    }

    /**
     * Returns true if this line is allready in a report of the supplied type, meaning it has been reported.
     * <p>
     * @param reportType the reportType.
     * @return true if this line is allready in a report of the supplied type, meaning it has been reported.
     */
    public boolean isInReport(TradeName reportType) {
        for (Report report : reports) {
            if (report.getType() == reportType)
                return true;
        }
        return false;
    }

    /**
     * Returns the ReportLine matching the type and limited to the same dossier.
     * <p>
     * @param documentTypes one of the types that the reference must have
     * @return the ReportLine matching the type and optional limited to the same dossier or null if no match.
     */
    public ReportLine getSingleReference(DocumentType... documentTypes) {
        return getSingleReference(null, documentTypes);
    }

    /**
     * Returns the ReportLine matching the type and limited to the same dossier.
     * <p>
     * @param positionType  the type the position must have, if null is ignored.
     * @param documentTypes one of the types that the reference must have
     * @return the ReportLine matching the type and optional limited to the same dossier or null if no match.
     */
    public ReportLine getSingleReference(PositionType positionType, DocumentType... documentTypes) {
        // TODO: There is a possibility, that more than one element matches. Not taken into account for now.
        for (ReportLine line : refrences) {
            if (!Arrays.asList(documentTypes).contains(line.getDocumentType()))
                continue;
            if (positionType != null && line.getPositionType() != positionType)
                continue;
            if (this.dossierId != line.dossierId)
                continue;
            return line;
        }
        return null;
    }

    public boolean hasReferenceTo(PositionType positionType, DocumentType... documentTypes) {
        return getSingleReference(positionType, documentTypes) != null;
    }

    /**
     * Returns true if this line gets discharged by some reference.
     * <p>
     * @return true if this line gets discharged by some reference.
     */
    // TODO: This is incomplete, if we have multipe complaints on one position in the same dossier. Hope this dosn't happen for a while. See http://overload.ahrensburg.gg-net.de/jira/browse/DW-1357
    public boolean isDischarged() {
        for (ReportLine ref : refrences) {
            if (this.dossierId == ref.dossierId && ref.getDocumentType() == DocumentType.COMPLAINT
                    && ref.getPositionType() == PositionType.UNIT
                    && ref.getWorkflowStatus() == WorkflowStatus.DISCHARGED) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if and only if there are existing repayments of type unit_annex on the same dossier.
     * In the case, than one or more repayment with a unit_annex and a repayment with unit exists, this method will return false.
     * <p>
     * @return true if and only if there are existing repayments of type unit_annex on the same dossier.
     */
    public boolean isPartialRepayed() {
        if (isFullRepayed())
            return false;
        // A partial repayment exists, so the true
        return getSingleReference(PositionType.UNIT_ANNEX, DocumentType.ANNULATION_INVOICE,
                DocumentType.CREDIT_MEMO) != null;
    }

    /**
     * Returns true if and only if there are existing repayments of type unit_annex on the same dossier and in the supplied collection.
     * In the case, than one or more repayment with a unit_annex and a repayment with unit exists, this method will return false.
     * <p>
     * @param other a collection to filter even more, for example by another report, must not be null.
     * @return true if and only if there are existing repayments of type unit_annex on the same dossier and in the supplied collection.
     * @throws NullPointerException if other is null
     */
    public boolean isPartialRepayedIn(Collection<ReportLine> other) throws NullPointerException {
        if (isFullRepayedIn(other))
            return false;
        // A partial repayment, so the true
        ReportLine repaymentRef = getSingleReference(PositionType.UNIT_ANNEX, DocumentType.ANNULATION_INVOICE,
                DocumentType.CREDIT_MEMO);
        if (repaymentRef == null)
            return false;
        return Objects.requireNonNull(other, "Other must not be null").contains(repaymentRef);
    }

    /**
     * Returns true if this line hasNoRepayments.
     * <p>
     * @return true if this line hasNoRepayments.
     */
    public boolean hasNoRepayments() {
        if (isFullRepayed())
            return false;
        if (isPartialRepayed())
            return false;
        return true;
    }

    /**
     * Returns true if this line has no open Complaints, either no complaints at all, or a complaint, that is discharged.
     * <p>
     * @return true if this line has no open Complaints, either no complaints at all, or a complaint, that is discharged.
     */
    public boolean hasNoOpenComplaints() {
        if (!hasReferenceTo(UNIT, COMPLAINT))
            return true;
        if (isDischarged())
            return true;
        return false;
    }

    /**
     * Returns ture if there exists a repayment of type unit on the same dossier.
     * <p>
     * @return ture if there exists a repayment of type unit on the same dossier.
     */
    public boolean isFullRepayed() {
        // ShortCut
        if (refrences.isEmpty())
            return false;
        return getSingleReference(PositionType.UNIT, DocumentType.ANNULATION_INVOICE,
                DocumentType.CREDIT_MEMO) != null;
    }

    /**
     * Returns ture if there exists a repayment of type unit on the same dossier and in the supplied collection.
     * <p>
     * @param other a collection to filter down the range, must not be null.
     * @return ture if there exists a repayment of type unit on the same dossier and in the supplied collection.
     * @throws NullPointerException if other is null
     */
    public boolean isFullRepayedIn(Collection<ReportLine> other) throws NullPointerException {
        // ShortCut
        if (refrences.isEmpty() || other == null || other.isEmpty())
            return false;
        // The ?: needed for the case that a unit_annex is asked if it has been full repaied.
        ReportLine repaymentRef = getSingleReference(getPositionType() == UNIT_ANNEX ? UNIT : getPositionType(),
                DocumentType.ANNULATION_INVOICE, DocumentType.CREDIT_MEMO);
        // No full repayment exists
        if (repaymentRef == null)
            return false;
        return other.contains(repaymentRef);
    }

    /**
     * Convenience Method for documentType is repayment and posisitionType is unit annex.
     * <p>
     * @return ture if documentType is repayment and posisitionType is unit annex.
     */
    public boolean isPartialRepayment() {
        return (documentType == CREDIT_MEMO || documentType == ANNULATION_INVOICE)
                && positionType == PositionType.UNIT_ANNEX;
    }

    /**
     * Convenience Method for documentType is repayment and posisitionType is unit.
     * <p>
     * @return ture if documentType is repayment and posisitionType is unit.
     */
    public boolean isFullRepayment() {
        return (documentType == CREDIT_MEMO || documentType == ANNULATION_INVOICE)
                && (positionType == UNIT || positionType == PRODUCT_BATCH);
        // TODO: A much safer way would be to verify that the amount and the price result = 0. But it should be okay for now.
    }

    /**
     * Returns a Set of document types, which are references exist.
     * <p>
     * @return a Set of document types, which are references exist.
     */
    public NavigableSet<DocumentType> getReferenceTypes() {
        NavigableSet<DocumentType> types = new TreeSet<>();
        for (ReportLine ref : refrences) {
            types.add(ref.getDocumentType());
        }
        return types;
    }

    public void setAddedToReport(boolean report) {
        if (addedToReportProperty == null)
            addedToReportProperty = new SimpleBooleanProperty(report);
        else
            addedToReportProperty.set(report);
    }

    public BooleanProperty addedToReportProperty() {
        return addedToReportProperty;
    }

    public boolean isAddedToReport() {
        return addedToReportProperty == null ? false : addedToReportProperty.get();
    }

    @Override
    public int compareTo(ReportLine o) {
        if (this.dossierId != o.dossierId)
            return Long.valueOf(this.dossierId).compareTo(o.dossierId);
        if (this.documentId != o.documentId)
            return Long.valueOf(this.documentId).compareTo(o.documentId);
        if (!this.documentType.equals(o.documentType))
            return this.documentType.compareTo(o.documentType);
        // Fallback to equality.
        return Integer.valueOf(this.hashCode()).compareTo(o.hashCode());
    }

    /**
     * Returns a correct name , either using the product or the name.
     * <p/>
     * @return a correct name , either using the product or the name.
     */
    public String toName() {
        if (StringUtils.isBlank(this.getProductBrandName()) || StringUtils.isBlank(this.getProductName()))
            return this.getName();
        return this.getProductBrandName() + " " + this.getProductName();
    }

    /**
     * Returns a Storable of this ReportLine.
     * <p>
     * @return a Storable of this ReportLine.
     */
    public Storeable toStorable() {
        return new Storeable(id, marginPercentage, purchasePrice);
    }

    /**
     * Eager Fetch Helper.
     */
    @Override
    public void fetchEager() {
        EagerHelper eagerHelper = new EagerHelper();
        eagerHelper.fetch(this);
    }

    @Override
    public String toString() {
        return "ReportLine{" + "id=" + id + ", reportingDate=" + reportingDate + ", dossierId=" + dossierId
                + ", dossierIdentifier=" + dossierIdentifier + ", documentId=" + documentId
                + ", documentIdentifier=" + documentIdentifier + ", documentType=" + documentType
                + ", documentTypeName=" + documentTypeName + ", actual=" + actual + ", workflowStatus="
                + workflowStatus + ", amount=" + amount + ", name=" + name + ", description=" + description
                + ", positionType=" + positionType + ", positionTypeName=" + positionTypeName + ", price=" + price
                + ", tax=" + tax + ", afterTaxPrice=" + afterTaxPrice + ", bookingAccount=" + bookingAccount
                + ", customerId=" + customerId + ", invoiceAddress=" + invoiceAddress + ", productGroup="
                + productGroup + ", productGroupName=" + productGroupName + ", productBrand=" + productBrand
                + ", productBrandName=" + productBrandName + ", productName=" + productName + ", partNo=" + partNo
                + ", manufacturerCostPrice=" + manufacturerCostPrice + ", productId=" + productId + ", refurbishId="
                + refurbishId + ", serial=" + serial + ", mfgDate=" + mfgDate + ", uniqueUnitId=" + uniqueUnitId
                + ", marginPercentage=" + marginPercentage + ", purchasePrice=" + purchasePrice + ", salesChannel="
                + salesChannel + ", salesChannelName=" + salesChannelName + ", contractor=" + contractor
                + ", contractorName=" + contractorName + ", contractorPartNo=" + contractorPartNo
                + ", contractorReferencePrice=" + contractorReferencePrice + '}';
    }

    /**
     * Returns a simpler String.
     * <p>
     * @return a simpler String.
     */
    public String toSimple() {
        return "ReportLine{" + "id=" + id + ", refurbishId=" + refurbishId + ", reportingDate=" + reportingDate
                + ", mfgDate=" + mfgDate + '}';
    }

}