/////////////////////////////////////////////////////////////////////////////
//
// 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.web.timesheet;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.ftlines.wicket.fullcalendar.Event;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.projectforge.calendar.TimePeriod;
import org.projectforge.common.DateHelper;
import org.projectforge.common.StringHelper;
import org.projectforge.core.OrderDirection;
import org.projectforge.fibu.KostFormatter;
import org.projectforge.fibu.ProjektDO;
import org.projectforge.fibu.kost.Kost2DO;
import org.projectforge.task.TaskDO;
import org.projectforge.timesheet.TimesheetDO;
import org.projectforge.timesheet.TimesheetDao;
import org.projectforge.timesheet.TimesheetFilter;
import org.projectforge.user.PFUserContext;
import org.projectforge.web.HtmlHelper;
import org.projectforge.web.calendar.ICalendarFilter;
import org.projectforge.web.calendar.MyEvent;
import org.projectforge.web.calendar.MyFullCalendarEventsProvider;
import org.projectforge.web.common.OutputType;
import org.projectforge.web.task.TaskFormatter;
/**
* Creates events for FullCalendar.
* @author Kai Reinhard (k.reinhard@micromata.de)
*
*/
public class TimesheetEventsProvider extends MyFullCalendarEventsProvider
{
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TimesheetEventsProvider.class);
private static final long serialVersionUID = 2241430630558260146L;
private final TimesheetDao timesheetDao;
private final ICalendarFilter calFilter;
private long totalDuration;
private Integer month;
private DateTime firstDayOfMonth;
private int days;
// duration by day of month.
private final long[] durationsPerDayOfMonth = new long[32];
private final long[] durationsPerDayOfYear = new long[380];
private Map<String, TimesheetDO> breaksMap;
private List<TimesheetDO> timesheets;
/**
* the name of the event class.
*/
public static final String EVENT_CLASS_NAME = "timesheet";
public static final String BREAK_EVENT_CLASS_NAME = "ts-break";
/**
* @param timesheetDao
* @param calFilter
*/
public TimesheetEventsProvider(final TimesheetDao timesheetDao, final ICalendarFilter calFilter)
{
this.timesheetDao = timesheetDao;
this.calFilter = calFilter;
}
/**
* @see org.projectforge.web.calendar.MyFullCalendarEventsProvider#buildEvents(org.joda.time.DateTime, org.joda.time.DateTime)
*/
@Override
protected void buildEvents(final DateTime start, final DateTime end)
{
totalDuration = 0;
for (int i = 0; i < durationsPerDayOfMonth.length; i++) {
durationsPerDayOfMonth[i] = 0;
}
for (int i = 0; i < durationsPerDayOfYear.length; i++) {
durationsPerDayOfYear[i] = 0;
}
final Integer userId = calFilter.getTimesheetUserId();
if (userId == null) {
return;
}
breaksMap = new HashMap<String, TimesheetDO>();
int breaksCounter = 0;
final TimesheetFilter filter = new TimesheetFilter();
filter.setUserId(userId);
filter.setStartTime(start.toDate());
filter.setStopTime(end.toDate());
filter.setOrderType(OrderDirection.ASC);
timesheets = timesheetDao.getList(filter);
boolean longFormat = false;
days = Days.daysBetween(start, end).getDays();
if (days < 10) {
// Week or day view:
longFormat = true;
month = null;
firstDayOfMonth = null;
} else {
// Month view:
final DateTime currentMonth = new DateTime(start.plusDays(10), PFUserContext.getDateTimeZone()); // Now we're definitely in the right
// month.
month = currentMonth.getMonthOfYear();
firstDayOfMonth = currentMonth.withDayOfMonth(1);
}
if (CollectionUtils.isEmpty(timesheets) == false) {
DateTime lastStopTime = null;
for (final TimesheetDO timesheet : timesheets) {
final DateTime startTime = new DateTime(timesheet.getStartTime(), PFUserContext.getDateTimeZone());
final DateTime stopTime = new DateTime(timesheet.getStopTime(), PFUserContext.getDateTimeZone());
if (stopTime.isBefore(start) == true || startTime.isAfter(end) == true) {
// Time sheet doesn't match time period start - end.
continue;
}
if (calFilter.isShowBreaks() == true) {
if (lastStopTime != null
&& DateHelper.isSameDay(stopTime, lastStopTime) == true
&& startTime.getMillis() - lastStopTime.getMillis() > 60000) {
// Show breaks between time sheets of one day (> 60s).
final Event breakEvent = new Event();
breakEvent.setEditable(false);
final String breakId = String.valueOf(++breaksCounter);
breakEvent.setClassName(BREAK_EVENT_CLASS_NAME).setId(breakId).setStart(lastStopTime).setEnd(startTime)
.setTitle(getString("timesheet.break"));
breakEvent.setTextColor("#666666").setBackgroundColor("#F9F9F9").setColor("#F9F9F9");
events.put(breakId, breakEvent);
final TimesheetDO breakTimesheet = new TimesheetDO().setStartDate(lastStopTime.toDate()).setStopTime(startTime.getMillis());
breaksMap.put(breakId, breakTimesheet);
}
lastStopTime = stopTime;
}
final long duration = timesheet.getDuration();
final MyEvent event = new MyEvent();
final String id = "" + timesheet.getId();
event.setClassName(EVENT_CLASS_NAME);
event.setId(id);
event.setStart(startTime);
event.setEnd(stopTime);
final String title = getTitle(timesheet);
if (longFormat == true) {
// Week or day view:
event.setTitle(title + "\n" + getToolTip(timesheet) + "\n" + formatDuration(duration, false));
} else {
// Month view:
event.setTitle(title);
}
if (month != null && startTime.getMonthOfYear() != month && stopTime.getMonthOfYear() != month) {
// Display time sheets of other month as grey blue:
event.setTextColor("#222222").setBackgroundColor("#ACD9E8").setColor("#ACD9E8");
}
events.put(id, event);
if (month == null || startTime.getMonthOfYear() == month) {
totalDuration += duration;
addDurationOfDay(startTime.getDayOfMonth(), duration);
}
final int dayOfYear = startTime.getDayOfYear();
addDurationOfDayOfYear(dayOfYear, duration);
event.setTooltip(
getString("timesheet"),
new String[][] {
{ title},
{ timesheet.getLocation(), getString("timesheet.location")},
{ KostFormatter.formatLong(timesheet.getKost2()), getString("fibu.kost2")},
{ TaskFormatter.instance().getTaskPath(timesheet.getTaskId(), true, OutputType.PLAIN),
getString("task")},
{ timesheet.getDescription(), getString("description")}});
}
}
if (calFilter.isShowStatistics() == true) {
// Show statistics: duration of every day is shown as all day event.
DateTime day = start;
final Calendar cal = DateHelper.getCalendar();
cal.setTime(start.toDate());
final int numberOfDaysInYear = cal.getActualMaximum(Calendar.DAY_OF_YEAR);
int paranoiaCounter = 0;
do {
if (++paranoiaCounter > 1000) {
log.error("Paranoia counter exceeded! Dear developer, please have a look at the implementation of buildEvents.");
break;
}
final int dayOfYear = day.getDayOfYear();
final long duration = durationsPerDayOfYear[dayOfYear];
final boolean firstDayOfWeek = day.getDayOfWeek() == PFUserContext.getJodaFirstDayOfWeek();
if (firstDayOfWeek == false && duration == 0) {
day = day.plusDays(1);
continue;
}
final Event event = new Event().setAllDay(true);
final String id = "s-" + (dayOfYear);
event.setId(id);
event.setStart(day);
final String durationString = formatDuration(duration, false);
if (firstDayOfWeek == true) {
// Show week of year at top of first day of week.
long weekDuration = 0;
for (short i = 0; i < 7; i++) {
int d = dayOfYear + i;
if (d > numberOfDaysInYear) {
d -= numberOfDaysInYear;
}
weekDuration += durationsPerDayOfYear[d];
}
final StringBuffer buf = new StringBuffer();
buf.append(getString("calendar.weekOfYearShortLabel")).append(DateHelper.getWeekOfYear(day));
if (days > 1 && weekDuration > 0) {
// Show total sum of durations over all time sheets of current week (only in week and month view).
buf.append(": ").append(formatDuration(weekDuration, false));
}
if (duration > 0) {
buf.append(", ").append(durationString);
}
event.setTitle(buf.toString());
} else {
event.setTitle(durationString);
}
event.setTextColor("#666666").setBackgroundColor("#F9F9F9").setColor("#F9F9F9");
event.setEditable(false);
events.put(id, event);
day = day.plusDays(1);
} while (day.isAfter(end) == false);
}
}
public TimesheetDO getBreakTimesheet(final String id)
{
return breaksMap != null ? breaksMap.get(id) : null;
}
public TimesheetDO getLatestTimesheetOfDay(final DateTime date)
{
if (timesheets == null) {
return null;
}
TimesheetDO latest = null;
for (final TimesheetDO timesheet : timesheets) {
if (DateHelper.isSameDay(timesheet.getStopTime(), date.toDate()) == true) {
if (latest == null) {
latest = timesheet;
} else if (latest.getStopTime().before(timesheet.getStopTime()) == true) {
latest = timesheet;
}
}
}
return latest;
}
public String formatDuration(final long millis)
{
return formatDuration(millis, firstDayOfMonth != null);
}
private String formatDuration(final long millis, final boolean showTimePeriod)
{
final int[] fields = TimePeriod.getDurationFields(millis, 8, 200);
final StringBuffer buf = new StringBuffer();
if (fields[0] > 0) {
buf.append(fields[0]).append(PFUserContext.getLocalizedString("calendar.unit.day")).append(" ");
}
buf.append(fields[1]).append(":").append(StringHelper.format2DigitNumber(fields[2]))
.append(PFUserContext.getLocalizedString("calendar.unit.hour"));
if (showTimePeriod == true) {
buf.append(" (").append(PFUserContext.getLocalizedString("calendar.month")).append(")");
}
return buf.toString();
}
public static String getTitle(final TimesheetDO timesheet)
{
final Kost2DO kost2 = timesheet.getKost2();
final TaskDO task = timesheet.getTask();
if (kost2 == null) {
return (task != null && task.getTitle() != null) ? HtmlHelper.escapeXml(task.getTitle()) : "";
}
final StringBuffer buf = new StringBuffer();
final StringBuffer b2 = new StringBuffer();
final ProjektDO projekt = kost2.getProjekt();
if (projekt != null) {
// final KundeDO kunde = projekt.getKunde();
// if (kunde != null) {
// if (StringUtils.isNotBlank(kunde.getIdentifier()) == true) {
// b2.append(kunde.getIdentifier());
// } else {
// b2.append(kunde.getName());
// }
// b2.append(" - ");
// }
if (StringUtils.isNotBlank(projekt.getIdentifier()) == true) {
b2.append(projekt.getIdentifier());
} else {
b2.append(projekt.getName());
}
} else {
b2.append(kost2.getDescription());
}
buf.append(StringUtils.abbreviate(b2.toString(), 30));
return buf.toString();
}
public static String getToolTip(final TimesheetDO timesheet)
{
final String location = timesheet.getLocation();
final String description = timesheet.getShortDescription();
final TaskDO task = timesheet.getTask();
final StringBuffer buf = new StringBuffer();
if (StringUtils.isNotBlank(location) == true) {
buf.append(location);
if (StringUtils.isNotBlank(description) == true) {
buf.append(": ");
}
}
buf.append(description);
if (timesheet.getKost2() == null) {
buf.append("; \n").append(task.getTitle());
}
return buf.toString();
}
/**
* @return the duration
*/
public long getTotalDuration()
{
return totalDuration;
}
private void addDurationOfDay(final int dayOfMonth, final long duration)
{
durationsPerDayOfMonth[dayOfMonth] += duration;
}
private void addDurationOfDayOfYear(final int dayOfYear, final long duration)
{
durationsPerDayOfYear[dayOfYear] += duration;
}
}