///////////////////////////////////////////////////////////////////////////// // // 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; } }