/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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 org.projectforge.plugins.liquidityplanning;
import java.io.Serializable;
import java.sql.Date;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.projectforge.calendar.DayHolder;
import org.projectforge.fibu.EingangsrechnungDO;
import org.projectforge.fibu.KontoCache;
import org.projectforge.fibu.KontoDO;
import org.projectforge.fibu.KundeDO;
import org.projectforge.fibu.KundeFormatter;
import org.projectforge.fibu.ProjektDO;
import org.projectforge.fibu.ProjektFormatter;
import org.projectforge.fibu.RechnungDO;
import org.projectforge.registry.Registry;
import org.projectforge.statistics.IntAggregatedValues;
/**
* @author Kai Reinhard (k.reinhard@micromata.de)
*
*/
public class LiquidityForecast implements Serializable
{
private static final long serialVersionUID = 5385319337895942452L;
private final List<LiquidityEntry> entries = new LinkedList<LiquidityEntry>();
private Collection<LiquidityEntry> liquiEntries;
private Collection<RechnungDO> invoices;
private Collection<LiquidityEntry> invoicesLiquidityEntries;
/**
* Used for calculating the expected date of payment for future invoices.
*/
private final Map<String, IntAggregatedValues> aggregatedDebitorInvoicesValuesMap = new HashMap<String, IntAggregatedValues>();
/**
* Used for calculating the expected date of payment for future invoices.
*/
private final Map<String, IntAggregatedValues> aggregatedCreditorInvoicesValuesMap = new HashMap<String, IntAggregatedValues>();
private Collection<EingangsrechnungDO> creditorInvoices;
private Collection<LiquidityEntry> creditorInvoicesLiquidityEntries;
/**
* Refresh forecast from stored liqui-entries, invoices and creditor invoices and sort the entries.
* @return this for chaining.
* @see #sort()
*/
public LiquidityForecast build()
{
entries.clear();
entries.addAll(this.liquiEntries);
entries.addAll(this.invoicesLiquidityEntries);
entries.addAll(this.creditorInvoicesLiquidityEntries);
sort();
return this;
}
/**
* @return this for chaining.
*/
private LiquidityForecast sort()
{
Collections.sort(entries, new Comparator<LiquidityEntry>() {
@Override
public int compare(final LiquidityEntry o1, final LiquidityEntry o2)
{
if (o1.getDateOfPayment() == null) {
if (o2.getDateOfPayment() != null) {
return -1;
}
} else if (o2.getDateOfPayment() == null) {
return 1;
} else {
final int compare = o1.getDateOfPayment().compareTo(o2.getDateOfPayment());
if (compare != 0) {
return compare;
}
}
final String s1 = o1.getSubject() != null ? o1.getSubject() : "";
final String s2 = o2.getSubject() != null ? o2.getSubject() : "";
return s1.compareTo(s2);
}
});
return this;
}
/**
* @return the entries
*/
public List<LiquidityEntry> getEntries()
{
return entries;
}
public LiquidityForecast set(final Collection<LiquidityEntryDO> list)
{
this.liquiEntries = new LinkedList<LiquidityEntry>();
if (list == null) {
return this;
}
for (final LiquidityEntryDO liquiEntry : list) {
final LiquidityEntry entry = new LiquidityEntry();
entry.setDateOfPayment(liquiEntry.getDateOfPayment());
entry.setAmount(liquiEntry.getAmount());
entry.setPaid(liquiEntry.isPaid());
entry.setSubject(liquiEntry.getSubject());
entry.setType(LiquidityEntryType.LIQUIDITY);
this.liquiEntries.add(entry);
}
return this;
}
/**
* For calculating the expected date of payment of future invoices. <br/>
* Should be called before {@link #setInvoices(Collection)}!
* @param list
*/
public LiquidityForecast calculateExpectedTimeOfPayments(final Collection<RechnungDO> list)
{
if (list == null) {
return this;
}
final KontoCache accountCache = Registry.instance().getKontoCache();
for (final RechnungDO invoice : list) {
final DayHolder date = new DayHolder(invoice.getDatum());
final DayHolder dateOfPayment = new DayHolder(invoice.getBezahlDatum());
if (date == null || dateOfPayment == null) {
continue;
}
final int timeForPayment = date.daysBetween(dateOfPayment);
final int amount = invoice.getGrossSum().intValue();
// Store values for different groups:
final Integer projectId = invoice.getProjektId();
if (projectId != null) {
ensureAndAddDebitorPaymentValue("project#" + projectId, timeForPayment, amount);
}
final Integer customerId = invoice.getKundeId();
if (customerId != null) {
ensureAndAddDebitorPaymentValue("customer#" + customerId, timeForPayment, amount);
}
final KontoDO account = accountCache.getKonto(invoice);
final Integer accountId = account != null ? account.getId() : null;
if (accountId != null) {
ensureAndAddDebitorPaymentValue("account#" + accountId, timeForPayment, amount);
}
String customerText = invoice.getKundeText();
if (customerText != null) {
customerText = customerText.toLowerCase();
ensureAndAddDebitorPaymentValue("customer:" + customerText, timeForPayment, amount);
if (customerText.length() > 5) {
customerText = customerText.substring(0, 5);
}
ensureAndAddDebitorPaymentValue("shortCustomer:" + customerText, timeForPayment, amount);
}
}
return this;
}
private void setExpectedTimeOfPayment(final LiquidityEntry entry, final RechnungDO invoice)
{
Date dateOfInvoice = invoice.getDatum();
if (dateOfInvoice == null) {
dateOfInvoice = new DayHolder().getSQLDate();
}
final ProjektDO project = invoice.getProjekt();
if (project != null
&& setExpectedDateOfPayment(entry, dateOfInvoice, "project#" + project.getId(),
ProjektFormatter.formatProjektKundeAsString(project, null, null)) == true) {
return;
}
final KundeDO customer = invoice.getKunde();
if (customer != null
&& setExpectedDateOfPayment(entry, dateOfInvoice, "customer#" + customer.getId(),
KundeFormatter.formatKundeAsString(customer, null)) == true) {
return;
}
final KontoCache accountCache = Registry.instance().getKontoCache();
final KontoDO account = accountCache.getKonto(invoice);
if (account != null
&& setExpectedDateOfPayment(entry, dateOfInvoice, "account#" + account.getId(),
"" + account.getNummer() + " - " + account.getBezeichnung()) == true) {
return;
}
String customerText = invoice.getKundeText();
if (customerText != null) {
customerText = customerText.toLowerCase();
if (setExpectedDateOfPayment(entry, dateOfInvoice, "customer:" + customerText, customerText) == true) {
return;
}
if (customerText.length() > 5) {
customerText = customerText.substring(0, 5);
}
if (setExpectedDateOfPayment(entry, dateOfInvoice, "shortCustomer:" + customerText, customerText) == true) {
return;
}
}
}
private boolean setExpectedDateOfPayment(final LiquidityEntry entry, final Date dateOfInvoice, final String mapKey, final String area)
{
final IntAggregatedValues values = aggregatedDebitorInvoicesValuesMap.get(mapKey);
if (values != null && values.getNumberOfValues() >= 1) {
entry.setExpectedDateOfPayment(getDate(dateOfInvoice, values.getWeightedAverage()));
entry.setComment(mapKey
+ ": "
+ area
+ ": "
+ values.getWeightedAverage()
+ " days ("
+ values.getNumberOfValues()
+ " paid invoices)");
return true;
} else {
return false;
}
}
private void ensureAndAddDebitorPaymentValue(final String mapId, final int timeForPayment, final int amount)
{
IntAggregatedValues values = aggregatedDebitorInvoicesValuesMap.get(mapId);
if (values == null) {
values = new IntAggregatedValues();
aggregatedDebitorInvoicesValuesMap.put(mapId, values);
}
values.add(timeForPayment, amount);
}
/**
* For calculating the expected date of payment of future invoices. <br/>
* Should be called before {@link #setInvoices(Collection)}!
* @param list
*/
public LiquidityForecast calculateExpectedTimeOfCreditorPayments(final Collection<EingangsrechnungDO> list)
{
if (list == null) {
return this;
}
for (final EingangsrechnungDO invoice : list) {
final DayHolder date = new DayHolder(invoice.getDatum());
final DayHolder dateOfPayment = new DayHolder(invoice.getBezahlDatum());
if (date == null || dateOfPayment == null) {
continue;
}
final int timeForPayment = date.daysBetween(dateOfPayment);
final int amount = invoice.getGrossSum().intValue();
final KontoDO account = invoice.getKonto();
final Integer accountId = account != null ? account.getId() : null;
if (accountId != null) {
ensureAndAddCreditorPaymentValue("account#" + accountId, timeForPayment, amount);
}
String creditorText = invoice.getKreditor();
if (creditorText != null) {
creditorText = creditorText.toLowerCase();
ensureAndAddCreditorPaymentValue("creditor:" + creditorText, timeForPayment, amount);
if (creditorText.length() > 5) {
creditorText = creditorText.substring(0, 5);
}
ensureAndAddCreditorPaymentValue("shortCreditor:" + creditorText, timeForPayment, amount);
}
}
return this;
}
private void setExpectedTimeOfPayment(final LiquidityEntry entry, final EingangsrechnungDO invoice)
{
Date dateOfInvoice = invoice.getDatum();
if (dateOfInvoice == null) {
dateOfInvoice = new DayHolder().getSQLDate();
}
final KontoDO account = invoice.getKonto();
if (account != null
&& setExpectedDateOfCreditorPayment(entry, dateOfInvoice, "account#" + account.getId(),
"" + account.getNummer() + " - " + account.getBezeichnung()) == true) {
return;
}
String creditorText = invoice.getKreditor();
if (creditorText != null) {
creditorText = creditorText.toLowerCase();
if (setExpectedDateOfCreditorPayment(entry, dateOfInvoice, "creditor:" + creditorText, creditorText) == true) {
return;
}
if (creditorText.length() > 5) {
creditorText = creditorText.substring(0, 5);
}
if (setExpectedDateOfCreditorPayment(entry, dateOfInvoice, "shortCreditor:" + creditorText, creditorText) == true) {
return;
}
}
}
private boolean setExpectedDateOfCreditorPayment(final LiquidityEntry entry, final Date dateOfInvoice, final String mapKey,
final String area)
{
final IntAggregatedValues values = aggregatedCreditorInvoicesValuesMap.get(mapKey);
if (values != null && values.getNumberOfValues() >= 1) {
entry.setExpectedDateOfPayment(getDate(dateOfInvoice, values.getWeightedAverage()));
entry.setComment(mapKey
+ ": "
+ area
+ ": "
+ values.getWeightedAverage()
+ " days ("
+ values.getNumberOfValues()
+ " paid invoices)");
return true;
} else {
return false;
}
}
private void ensureAndAddCreditorPaymentValue(final String mapId, final int timeForPayment, final int amount)
{
IntAggregatedValues values = aggregatedCreditorInvoicesValuesMap.get(mapId);
if (values == null) {
values = new IntAggregatedValues();
aggregatedCreditorInvoicesValuesMap.put(mapId, values);
}
values.add(timeForPayment, amount);
}
private Date getDate(final Date date, final int timeOfPayment)
{
final DayHolder day = new DayHolder(date);
day.add(Calendar.DAY_OF_YEAR, timeOfPayment);
return day.getSQLDate();
}
/**
* Should be called after {@link #calculateExpectedTimeOfPayments(Collection)}-
* @param list
* @return
*/
public LiquidityForecast setInvoices(final Collection<RechnungDO> list)
{
this.invoices = list;
this.invoicesLiquidityEntries = new LinkedList<LiquidityEntry>();
if (list == null) {
return this;
}
for (final RechnungDO invoice : list) {
final LiquidityEntry entry = new LiquidityEntry();
if (invoice.getBezahlDatum() != null) {
entry.setDateOfPayment(invoice.getBezahlDatum());
} else {
entry.setDateOfPayment(invoice.getFaelligkeit());
}
entry.setAmount(invoice.getGrossSum());
entry.setPaid(invoice.isBezahlt());
entry.setSubject("#" + invoice.getNummer() + ": " + invoice.getKundeAsString() + ": " + invoice.getBetreff());
entry.setType(LiquidityEntryType.DEBITOR);
setExpectedTimeOfPayment(entry, invoice);
this.invoicesLiquidityEntries.add(entry);
}
return this;
}
public LiquidityForecast setCreditorInvoices(final Collection<EingangsrechnungDO> list)
{
this.creditorInvoices = list;
this.creditorInvoicesLiquidityEntries = new LinkedList<LiquidityEntry>();
if (list == null) {
return this;
}
for (final EingangsrechnungDO invoice : list) {
final LiquidityEntry entry = new LiquidityEntry();
if (invoice.getBezahlDatum() != null) {
entry.setDateOfPayment(invoice.getBezahlDatum());
} else {
entry.setDateOfPayment(invoice.getFaelligkeit());
}
entry.setAmount(invoice.getGrossSum().negate());
entry.setPaid(invoice.isBezahlt());
entry.setSubject(invoice.getKreditor() + ": " + invoice.getBetreff());
entry.setType(LiquidityEntryType.CREDITOR);
setExpectedTimeOfPayment(entry, invoice);
this.creditorInvoicesLiquidityEntries.add(entry);
}
return this;
}
/**
* @return the invoices
*/
public Collection<LiquidityEntry> getInvoicesLiquidityEntries()
{
return invoicesLiquidityEntries;
}
/**
* @return the invoices
*/
public Collection<RechnungDO> getInvoices()
{
return invoices;
}
/**
* @return the creditorInvoices
*/
public Collection<LiquidityEntry> getCreditorInvoicesLiquidityEntries()
{
return creditorInvoicesLiquidityEntries;
}
/**
* @return the creditorInvoices
*/
public Collection<EingangsrechnungDO> getCreditorInvoices()
{
return creditorInvoices;
}
}