/*
* 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.report.entity;
import java.io.Serializable;
import java.util.*;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.time.DateUtils;
import org.metawidget.inspector.annotation.UiLarge;
import eu.ggnet.dwoss.rules.DocumentType;
import eu.ggnet.dwoss.rules.TradeName;
import eu.ggnet.dwoss.util.DateFormats;
import eu.ggnet.dwoss.util.persistence.EagerAble;
import eu.ggnet.dwoss.util.persistence.entity.IdentifiableEntity;
import lombok.*;
import static eu.ggnet.dwoss.report.entity.Report.ViewMode.DEFAULT;
//TODO: Name: Zusammenfassung, Comulation. Gruppierung.
/**
*
* @author bastian.venz
* @has 1 - n ReportLine
*/
@Entity
@NoArgsConstructor
public class Report extends IdentifiableEntity implements Serializable, EagerAble {
/**
* The ViewModw of a Report.
* A Viewmode allowed different looks and filterings for the ui.
* <p>
*/
public enum ViewMode {
/**
* Default.
*/
DEFAULT,
/**
* Splitt the Invoiced Lines by the mfgDate of each unit and creates an extra view for warranties.
*/
YEARSPLITT_AND_WARRANTIES
}
@Value
public static class YearSplit implements Serializable {
private final Date splitter;
/**
* Contains lines from splitter till today.
*/
private final NavigableSet<ReportLine> before;
/**
* Contains lines from 1970 till splitter.
*/
private final NavigableSet<ReportLine> after;
}
@Id
@GeneratedValue
@Getter
private long id;
@Version
private int optLock;
@NotNull
@Getter
@Setter
private String name;
/**
* This is the type of report.
* This value should never be changed afterwards.
*/
@Getter
@NotNull
private TradeName type;
/**
* This String is a representation the contractor for who the report was generated.
* If this is null its represent the AllReport.
*/
@Getter
private String typeName;
/**
* This date represent a point where the span of the report start.
*/
@NotNull
@Temporal(javax.persistence.TemporalType.DATE)
@Getter
@Setter
private Date startingDate;
/**
* This date represent a point where the span of the report ends.
*/
@NotNull
@Temporal(javax.persistence.TemporalType.DATE)
@Getter
@Setter
private Date endingDate;
@Lob
@Getter
@Setter
@UiLarge
@Column(length = 65536)
private String comment;
@Getter
@NotNull
private ViewMode viewMode = DEFAULT;
/**
* This set contains the ReportsLines of the Reports, where is the mapping unidirectional.
*/
@ManyToMany
private Set<ReportLine> lines = new HashSet<>();
public Report(String name, TradeName type, Date startingDate, Date endingDate, ViewMode viewMode) {
this(name, type, startingDate, endingDate);
this.viewMode = viewMode;
}
public Report(String name, TradeName type, Date startingDate, Date endingDate) {
this.name = name;
this.type = Objects.requireNonNull(type, "The type must not be null");
this.typeName = type.name();
this.startingDate = startingDate;
this.endingDate = endingDate;
}
/**
* Add a ReportLine to the Set of ReportLines.
* <p/>
* @param reportLine the ReportLine that will be added.
*/
public void add(ReportLine reportLine) {
if ( reportLine == null ) return;
lines.add(reportLine);
reportLine.reports.add(this);
}
/**
* Remove a ReportLine from the Set of ReportLines
* <p/>
* @param reportLine the ReportLine that will be removed.
*/
public void remove(ReportLine reportLine) {
if ( reportLine == null ) return;
lines.remove(reportLine);
reportLine.reports.remove(this);
}
public void addAll(Collection<? extends ReportLine> reportLines) {
for (ReportLine reportLine : reportLines) {
add(reportLine);
}
}
public NavigableSet<ReportLine> getLines() {
return new TreeSet<>(lines);
}
/**
* Returns all Lines of the Report for Category Invoiced, split by mfgDate - startOfReport < 1 year and the rest.
* This consists of:
* <ul>
* <li>Position of Type Invoice, with no References</li>
* <li>Position of Type UNIT_ANNEX in DocumentType CREDIT_MEMO/ANNULATIION_INVOICE and a Referencing Invoice in the same report.</li>
* </ul>
* <p>
* @return all Lines of the Report for Category Invoiced.
*/
public YearSplit filterInvoicedSplit() {
NavigableSet<ReportLine> pastSplit = filterInvoiced();
NavigableSet<ReportLine> preSplit = new TreeSet<>();
Date splitter = DateUtils.addYears(startingDate, -1);
for (ReportLine line : pastSplit) {
if ( splitter.before(line.getMfgDate()) ) preSplit.add(line);
}
pastSplit.removeAll(preSplit);
return new YearSplit(startingDate, preSplit, pastSplit);
}
/**
* Returns all Lines of the Report for Category Invoiced.
* This consists of:
* <ul>
* <li>Position of Type Capital Asset</li>
* <li>Position of Type Invoice, with no References</li>
* <li>Position of Type UNIT_ANNEX in DocumentType CREDIT_MEMO/ANNULATIION_INVOICE and a Referencing Invoice in the same report.</li>
* </ul>
* <p>
* @return all Lines of the Report for Category Invoiced.
*/
//TODO: We could also substract the value of a unitannex from the invoice and not return the unit annex at all.
//But consider the impact in the ui, especially if we allow selection of such a "combined" line.
public NavigableSet<ReportLine> filterInvoiced() {
NavigableSet<ReportLine> result = new TreeSet<>();
for (ReportLine line : lines) {
if ( line.getDocumentType() == DocumentType.CAPITAL_ASSET ) result.add(line); // There is no way a capital Asset can be returned.
// Only if we are fully repayed in this report, we are not in the invoiced result.
if ( line.getDocumentType() == DocumentType.INVOICE && !line.isFullRepayedIn(lines) ) result.add(line);
if ( line.isPartialRepayment() && !line.isFullRepayedIn(lines) && lines.contains(line.getSingleReference(DocumentType.INVOICE)) ) result.add(line);
}
return result;
}
/**
* Returns all Lines of the Report which represent Positions of CreditMemos and Annulation Invoices but the associated Invoices have been reported before.
* This consists of:
* <ul>
* <li>Position of Type UNIT in DocumentType CREDIT_MEMO/ANNULATIION_INVOICE and a Referencing Invoice is not in same report.</li>
* <li>Position of Type UNIT_ANNEX in DocumentType CREDIT_MEMO/ANNULATIION_INVOICE and a Referencing Invoice is not in same report and no UNIT also in this
* report</li>
* </ul>
* <p>
* @return all Lines of the Report which represent Positions of CreditMemos and Annulation Invoices but the associated Invoices have been reported before.
*/
public NavigableSet<ReportLine> filterRepayed() {
NavigableSet<ReportLine> result = new TreeSet<>();
for (ReportLine line : lines) {
if ( line.isFullRepayment() && !lines.contains(line.getSingleReference(DocumentType.INVOICE)) ) result.add(line);
// Implies ( !lines.contaions(null) ) == true
if ( line.isPartialRepayment() && !line.isFullRepayedIn(lines) && !lines.contains(line.getSingleReference(DocumentType.INVOICE)) ) result.add(line);
}
return result;
}
/**
* Returns all Reportlines, that don't have an impact on the result, but have only informational character.
* <p>
* @return all Reportlines, that don't have an impact on the result, but have only informational character.
*/
public NavigableSet<ReportLine> filterInfos() {
NavigableSet<ReportLine> result = new TreeSet<>(lines);
result.removeAll(filterInvoiced());
result.removeAll(filterRepayed());
return result;
}
@Override
public void fetchEager() {
ReportLine.EagerHelper eagerHelper = new ReportLine.EagerHelper();
eagerHelper.fetch(this);
}
/**
* Returns a String representation of the report, seperated by all filteroperations.
* <p>
* @param showSplit if true the invoiced part is also splitt by mfgDate and one year before the start
* @return a String representation of the report, seperated by all filteroperations.
*/
public String toMultiLine(boolean showSplit) {
String result = this.name + "\n";
if ( showSplit ) {
YearSplit splitresult = filterInvoicedSplit();
result += buildFilter("Invoiced Before " + DateFormats.ISO.format(splitresult.splitter), splitresult.before);
result += buildFilter("Invoiced After " + DateFormats.ISO.format(splitresult.splitter), splitresult.after);
} else {
result += buildFilter("Invoiced", filterInvoiced());
}
result += buildFilter("Repayed", filterRepayed());
result += buildFilter("Infos", filterInfos());
return result;
}
private String buildFilter(String head, Collection<ReportLine> lines) {
StringBuilder sb = new StringBuilder(" " + head);
if ( lines.isEmpty() ) return "";
sb.append("\n");
for (ReportLine line : lines) {
sb.append(" - ")
.append(line.getRefurbishId()).append("|")
.append(line.getDocumentTypeName()).append("|")
.append(line.getPositionTypeName()).append("|")
.append(line.getPrice()).append("|")
.append(line.getMfgDate() == null ? "no mfgDate" : DateFormats.ISO.format(line.getMfgDate())).append("\n");
}
sb.append("-----\n");
return sb.toString();
}
@Override
public String toString() {
return "Report{" + "id=" + id + ", name=" + name + ", type=" + type + ", typeName=" + typeName + ", startingDate=" + startingDate + ", endingDate=" + endingDate + ", comment=" + comment + '}';
}
}