///////////////////////////////////////////////////////////////////////////// // // 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.io.Serializable; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.SubmitLink; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; import org.hibernate.Hibernate; import org.projectforge.common.DateFormatType; import org.projectforge.common.DateFormats; import org.projectforge.common.DateHelper; import org.projectforge.common.DateHolder; import org.projectforge.common.FileHelper; import org.projectforge.common.MyBeanComparator; import org.projectforge.core.SystemInfoCache; import org.projectforge.jira.JiraUtils; import org.projectforge.renderer.PdfRenderer; import org.projectforge.renderer.custom.Formatter; import org.projectforge.renderer.custom.FormatterFactory; import org.projectforge.task.TaskDO; import org.projectforge.task.TaskTree; import org.projectforge.timesheet.TimesheetDO; import org.projectforge.timesheet.TimesheetDao; import org.projectforge.timesheet.TimesheetExport; import org.projectforge.timesheet.TimesheetFilter; import org.projectforge.user.PFUserContext; import org.projectforge.user.PFUserDO; import org.projectforge.user.UserGroupCache; import org.projectforge.user.UserPrefArea; import org.projectforge.web.HtmlHelper; import org.projectforge.web.calendar.DateTimeFormatter; import org.projectforge.web.task.TaskPropertyColumn; import org.projectforge.web.user.UserFormatter; import org.projectforge.web.user.UserPrefListPage; import org.projectforge.web.user.UserPropertyColumn; import org.projectforge.web.wicket.AbstractListPage; import org.projectforge.web.wicket.CellItemListener; import org.projectforge.web.wicket.CellItemListenerPropertyColumn; import org.projectforge.web.wicket.DownloadUtils; import org.projectforge.web.wicket.IListPageColumnsCreator; import org.projectforge.web.wicket.ListPage; import org.projectforge.web.wicket.ListSelectActionPanel; import org.projectforge.web.wicket.MyListPageSortableDataProvider; import org.projectforge.web.wicket.WicketUtils; import org.projectforge.web.wicket.components.ContentMenuEntryPanel; import org.projectforge.web.wicket.flowlayout.CheckBoxPanel; import org.springframework.util.CollectionUtils; @ListPage(editPage = TimesheetEditPage.class) public class TimesheetListPage extends AbstractListPage<TimesheetListForm, TimesheetDao, TimesheetDO> implements IListPageColumnsCreator<TimesheetDO> { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TimesheetListPage.class); protected static final String[] MY_BOOKMARKABLE_INITIAL_PROPERTIES = mergeStringArrays(BOOKMARKABLE_INITIAL_PROPERTIES, new String[] { "f.userId|user", "f.taskId|task", "f.startTime|t1", "f.stopTime|t2", "f.marked", "f.longFormat|long", "f.recursive"}); /** * Key for pre-setting the task id. */ public static final String PARAMETER_KEY_TASK_ID = "taskId"; public static final String PARAMETER_KEY_SEARCHSTRING = "searchString"; public static final String PARAMETER_KEY_USER_ID = "userId"; public static final String PARAMETER_KEY_START_TIME = "startTime"; public static final String PARAMETER_KEY_STOP_TIME = "stopTime"; public static final String PARAMETER_KEY_CLEAR_ALL = "clear"; private static final long serialVersionUID = 8582874051700734977L; @SpringBean(name = "dateTimeFormatter") private DateTimeFormatter dateTimeFormatter; @SpringBean(name = "formatterFactory") private FormatterFactory formatterFactory; @SpringBean(name = "pdfRenderer") private PdfRenderer pdfRenderer; @SpringBean(name = "timesheetDao") private TimesheetDao timesheetDao; @SpringBean(name = "timesheetExport") private TimesheetExport timesheetExport; @SpringBean(name = "taskTree") private TaskTree taskTree; @SpringBean(name = "userFormatter") private UserFormatter userFormatter; @SpringBean(name = "userGroupCache") private UserGroupCache userGroupCache; private TimesheetsICSExportDialog icsExportDialog; public TimesheetListPage(final PageParameters parameters) { super(parameters, "timesheet"); if (WicketUtils.contains(parameters, PARAMETER_KEY_CLEAR_ALL) == true) { final boolean clear = WicketUtils.getAsBoolean(parameters, PARAMETER_KEY_CLEAR_ALL); if (clear == true) { form.getSearchFilter().setTaskId(null); form.getSearchFilter().setSearchString(null); form.getSearchFilter().setUserId(null); form.getSearchFilter().setStartTime(null); form.getSearchFilter().setStopTime(null); form.getSearchFilter().setRecursive(true); form.getSearchFilter().setMarked(false); form.getSearchFilter().setDeleted(false); } } if (WicketUtils.contains(parameters, PARAMETER_KEY_TASK_ID) == true) { final Integer id = WicketUtils.getAsInteger(parameters, PARAMETER_KEY_TASK_ID); form.getSearchFilter().setTaskId(id); } if (WicketUtils.contains(parameters, PARAMETER_KEY_SEARCHSTRING) == true) { final String searchString = WicketUtils.getAsString(parameters, PARAMETER_KEY_SEARCHSTRING); form.getSearchFilter().setSearchString(searchString); } if (WicketUtils.contains(parameters, PARAMETER_KEY_USER_ID) == true) { final Integer id = WicketUtils.getAsInteger(parameters, PARAMETER_KEY_USER_ID); form.getSearchFilter().setUserId(id); } if (WicketUtils.contains(parameters, PARAMETER_KEY_START_TIME) == true) { final Long time = WicketUtils.getAsLong(parameters, PARAMETER_KEY_START_TIME); if (time != null) { form.getSearchFilter().setStartTime(new Date(time)); } else { form.getSearchFilter().setStartTime(null); } } if (WicketUtils.contains(parameters, PARAMETER_KEY_STOP_TIME) == true) { final Long time = WicketUtils.getAsLong(parameters, PARAMETER_KEY_STOP_TIME); if (time != null) { form.getSearchFilter().setStopTime(new Date(time)); } else { form.getSearchFilter().setStopTime(null); } } } @SuppressWarnings("serial") @Override protected void init() { final BookmarkablePageLink<Void> addTemplatesLink = UserPrefListPage.createLink("link", UserPrefArea.TIMESHEET_TEMPLATE); final ContentMenuEntryPanel menuEntry = new ContentMenuEntryPanel(getNewContentMenuChildId(), addTemplatesLink, getString("templates")); addContentMenuEntry(menuEntry); final ContentMenuEntryPanel exportMenu = new ContentMenuEntryPanel(getNewContentMenuChildId(), getString("export")); addContentMenuEntry(exportMenu); { final SubmitLink exportPDFButton = new SubmitLink(ContentMenuEntryPanel.LINK_ID, form) { @Override public void onSubmit() { exportPDF(); }; }; exportMenu.addSubMenuEntry(new ContentMenuEntryPanel(exportMenu.newSubMenuChildId(), exportPDFButton, getString("exportAsPdf")) .setTooltip(getString("tooltip.export.pdf"))); } { final SubmitLink exportExcelButton = new SubmitLink(ContentMenuEntryPanel.LINK_ID, form) { @Override public void onSubmit() { exportExcel(); }; }; exportMenu.addSubMenuEntry(new ContentMenuEntryPanel(exportMenu.newSubMenuChildId(), exportExcelButton, getString("exportAsXls")) .setTooltip(getString("tooltip.export.excel"))); } icsExportDialog = new TimesheetsICSExportDialog(newModalDialogId(), new ResourceModel("timesheet.iCalSubscription")); add(icsExportDialog); icsExportDialog.init(PFUserContext.getUserId()); icsExportDialog.redraw(); final AjaxLink<Void> icsExportDialogButton = new AjaxLink<Void>(ContentMenuEntryPanel.LINK_ID) { /** * @see org.apache.wicket.ajax.markup.html.AjaxLink#onClick(org.apache.wicket.ajax.AjaxRequestTarget) */ @Override public void onClick(final AjaxRequestTarget target) { icsExportDialog.open(target); }; }; // final IconLinkPanel exportICalButtonPanel = new IconLinkPanel(buttonGroupPanel.newChildId(), IconType.DOWNLOAD, // getString("timesheet.iCalSubscription"), iCalExportLink); exportMenu.addSubMenuEntry(new ContentMenuEntryPanel(exportMenu.newSubMenuChildId(), icsExportDialogButton, getString("timesheet.icsExport")).setTooltip(getString("timesheet.iCalSubscription"))); } @Override protected void onNextSubmit() { if (CollectionUtils.isEmpty(this.selectedItems) == true) { return; } final List<TimesheetDO> list = timesheetDao.internalLoad(this.selectedItems); setResponsePage(new TimesheetMassUpdatePage(this, list)); } @Override public boolean isSupportsMassUpdate() { return true; } @Override protected void createDataTable() { final List<IColumn<TimesheetDO, String>> columns = createColumns(this, !isMassUpdateMode(), isMassUpdateMode(), form.getSearchFilter(), taskTree, userFormatter, dateTimeFormatter); dataTable = createDataTable(columns, "startTime", SortOrder.DESCENDING); form.add(dataTable); } public List<IColumn<TimesheetDO, String>> createColumns(final WebPage returnToPage, final boolean sortable) { return createColumns(returnToPage, sortable, false, form.getSearchFilter(), taskTree, userFormatter, dateTimeFormatter); } /** * For re-usage in other pages. * @param page * @param isMassUpdateMode * @param timesheetFilter If given, then the long format filter setting will be used for displaying the description, otherwise the short * description is used. */ @SuppressWarnings("serial") protected static final List<IColumn<TimesheetDO, String>> createColumns(final WebPage page, final boolean sortable, final boolean isMassUpdateMode, final TimesheetFilter timesheetFilter, final TaskTree taskTree, final UserFormatter userFormatter, final DateTimeFormatter dateTimeFormatter) { final List<IColumn<TimesheetDO, String>> columns = new ArrayList<IColumn<TimesheetDO, String>>(); final CellItemListener<TimesheetDO> cellItemListener = new CellItemListener<TimesheetDO>() { public void populateItem(final Item<ICellPopulator<TimesheetDO>> item, final String componentId, final IModel<TimesheetDO> rowModel) { final TimesheetDO timesheet = rowModel.getObject(); final Serializable highlightedRowId; if (page instanceof AbstractListPage< ? , ? , ? >) { highlightedRowId = ((AbstractListPage< ? , ? , ? >) page).getHighlightedRowId(); } else { highlightedRowId = null; } appendCssClasses(item, timesheet.getId(), highlightedRowId, timesheet.isDeleted()); } }; if (page instanceof TimesheetMassUpdatePage) { columns.add(new UserPropertyColumn<TimesheetDO>(page.getString("timesheet.user"), getSortable("user.fullname", sortable), "user", cellItemListener).withUserFormatter(userFormatter)); } else { // Show first column not for TimesheetMassUpdatePage! if (isMassUpdateMode == true && page instanceof TimesheetListPage) { final TimesheetListPage timesheetListPage = (TimesheetListPage) page; columns.add(new CellItemListenerPropertyColumn<TimesheetDO>("", null, "selected", cellItemListener) { @Override public void populateItem(final Item<ICellPopulator<TimesheetDO>> item, final String componentId, final IModel<TimesheetDO> rowModel) { final TimesheetDO timesheet = rowModel.getObject(); final CheckBoxPanel checkBoxPanel = new CheckBoxPanel(componentId, timesheetListPage.new SelectItemModel(timesheet.getId()), null); item.add(checkBoxPanel); cellItemListener.populateItem(item, componentId, rowModel); addRowClick(item, isMassUpdateMode); } }); columns.add(new UserPropertyColumn<TimesheetDO>(page.getString("timesheet.user"), getSortable("user.fullname", sortable), "user", cellItemListener).withUserFormatter(userFormatter)); } else { columns.add(new UserPropertyColumn<TimesheetDO>(page.getString("timesheet.user"), getSortable("user.fullname", sortable), "user", cellItemListener) { @Override public void populateItem(final Item<ICellPopulator<TimesheetDO>> item, final String componentId, final IModel<TimesheetDO> rowModel) { item.add(new ListSelectActionPanel(componentId, rowModel, TimesheetEditPage.class, rowModel.getObject().getId(), page, getLabelString(rowModel))); cellItemListener.populateItem(item, componentId, rowModel); addRowClick(item); } }.withUserFormatter(userFormatter)); } } final SystemInfoCache systemInfoCache = SystemInfoCache.instance(); if (systemInfoCache.isCost2EntriesExists() == true) { columns.add(new CellItemListenerPropertyColumn<TimesheetDO>(new Model<String>(page.getString("fibu.kunde")), getSortable( "kost2.projekt.kunde.name", sortable), "kost2.projekt.kunde.name", cellItemListener)); columns.add(new CellItemListenerPropertyColumn<TimesheetDO>(new Model<String>(page.getString("fibu.projekt")), getSortable( "kost2.projekt.name", sortable), "kost2.projekt.name", cellItemListener)); } columns.add(new TaskPropertyColumn<TimesheetDO>(page.getString("task"), getSortable("task.title", sortable), "task", cellItemListener) .withTaskTree(taskTree)); if (systemInfoCache.isCost2EntriesExists() == true) { columns.add(new CellItemListenerPropertyColumn<TimesheetDO>(page.getString("fibu.kost2"), getSortable("kost2.shortDisplayName", sortable), "kost2.shortDisplayName", cellItemListener)); } columns.add(new CellItemListenerPropertyColumn<TimesheetDO>(page.getString("calendar.weekOfYearShortLabel"), getSortable( "formattedWeekOfYear", sortable), "formattedWeekOfYear", cellItemListener)); columns.add(new CellItemListenerPropertyColumn<TimesheetDO>(page.getString("calendar.dayOfWeekShortLabel"), getSortable("startTime", sortable), "startTime", cellItemListener) { @Override public void populateItem(final Item<ICellPopulator<TimesheetDO>> item, final String componentId, final IModel<TimesheetDO> rowModel) { final TimesheetDO timesheet = rowModel.getObject(); final Label label = new Label(componentId, dateTimeFormatter.getFormattedDate(timesheet.getStartTime(), DateFormats.getFormatString(DateFormatType.DAY_OF_WEEK_SHORT))); cellItemListener.populateItem(item, componentId, rowModel); item.add(label); } }); columns.add(new CellItemListenerPropertyColumn<TimesheetDO>(page.getString("timePeriod"), getSortable("startTime", sortable), "timePeriod", cellItemListener) { @Override public void populateItem(final Item<ICellPopulator<TimesheetDO>> item, final String componentId, final IModel<TimesheetDO> rowModel) { final TimesheetDO timesheet = rowModel.getObject(); final Label label = new Label(componentId, dateTimeFormatter.getFormattedTimePeriod(timesheet.getTimePeriod())); label.setEscapeModelStrings(false); cellItemListener.populateItem(item, componentId, rowModel); item.add(label); } }); columns.add(new CellItemListenerPropertyColumn<TimesheetDO>(page.getString("timesheet.duration"), getSortable("duration", sortable), "duration", cellItemListener) { @Override public void populateItem(final Item<ICellPopulator<TimesheetDO>> item, final String componentId, final IModel<TimesheetDO> rowModel) { final TimesheetDO timesheet = rowModel.getObject(); final Label label = new Label(componentId, dateTimeFormatter.getFormattedDuration(timesheet.getDuration())); label.setEscapeModelStrings(false); cellItemListener.populateItem(item, componentId, rowModel); item.add(label); } }); columns.add(new CellItemListenerPropertyColumn<TimesheetDO>(page.getString("timesheet.location"), getSortable("location", sortable), "location", cellItemListener)); columns.add(new CellItemListenerPropertyColumn<TimesheetDO>(page.getString("description"), getSortable("shortDescription", sortable), "shortDescription", cellItemListener) { @Override public void populateItem(final Item<ICellPopulator<TimesheetDO>> item, final String componentId, final IModel<TimesheetDO> rowModel) { final TimesheetDO timesheet = rowModel.getObject(); final Label label = new Label(componentId, new Model<String>() { @Override public String getObject() { String text; if (timesheetFilter != null && timesheetFilter.isLongFormat() == true) { text = HtmlHelper.escapeXml(timesheet.getDescription()); } else { text = HtmlHelper.escapeXml(timesheet.getShortDescription()); } if (isMassUpdateMode == true) { return text; } else { return JiraUtils.linkJiraIssues(text); // Not in mass update mode: link on table row results otherwises in JIRA-Link. } } }); label.setEscapeModelStrings(false); cellItemListener.populateItem(item, componentId, rowModel); item.add(label); } }); return columns; } @Override protected TimesheetListForm newListForm(final AbstractListPage< ? , ? , ? > parentPage) { return new TimesheetListForm(this); } @Override protected TimesheetDao getBaseDao() { return timesheetDao; } /** * @see org.projectforge.web.wicket.AbstractListPage#select(java.lang.String, java.lang.Object) */ @Override public void select(final String property, final Object selectedValue) { if ("taskId".equals(property) == true) { form.getSearchFilter().setTaskId((Integer) selectedValue); refresh(); } else if ("userId".equals(property) == true) { form.getSearchFilter().setUserId((Integer) selectedValue); refresh(); } else if (property.startsWith("quickSelect.") == true) { // month".equals(property) == true) { final Date date = (Date) selectedValue; form.getSearchFilter().setStartTime(date); final DateHolder dateHolder = new DateHolder(date); if (property.endsWith(".month") == true) { dateHolder.setEndOfMonth(); } else if (property.endsWith(".week") == true) { dateHolder.setEndOfWeek(); } else { log.error("Property '" + property + "' not supported for selection."); } form.getSearchFilter().setStopTime(dateHolder.getDate()); form.startDate.markModelAsChanged(); form.stopDate.markModelAsChanged(); refresh(); } else { super.select(property, selectedValue); } } /** * * @see org.projectforge.web.fibu.ISelectCallerPage#unselect(java.lang.String) */ @Override public void unselect(final String property) { if ("taskId".equals(property) == true) { form.getSearchFilter().setTaskId(null); refresh(); } else if ("userId".equals(property) == true) { form.getSearchFilter().setUserId(null); refresh(); } else { super.unselect(property); } } /** * @see org.projectforge.web.wicket.AbstractListPage#buildList() */ @Override protected List<TimesheetDO> buildList() { final TimesheetFilter filter = form.getSearchFilter(); if (filter.getStartTime() == null && filter.getStopTime() == null && filter.getTaskId() == null) { return null; } return super.buildList(); } void exportPDF() { refresh(); final List<TimesheetDO> timeSheets = getList(); if (timeSheets == null || timeSheets.size() == 0) { // Nothing to export. form.addError("validation.error.nothingToExport"); return; } final StringBuffer buf = new StringBuffer(); buf.append("timesheets_"); final TimesheetFilter filter = form.getSearchFilter(); if (filter.getUserId() != null) { buf.append(FileHelper.createSafeFilename(userGroupCache.getUser(filter.getUserId()).getLastname(), 20)).append("_"); } if (filter.getTaskId() != null) { final String taskTitle = taskTree.getTaskById(filter.getTaskId()).getTitle(); buf.append(FileHelper.createSafeFilename(taskTitle, 8)).append("_"); } buf.append(DateHelper.getDateAsFilenameSuffix(filter.getStartTime())).append("_") .append(DateHelper.getDateAsFilenameSuffix(filter.getStopTime())).append(".pdf"); final String filename = buf.toString(); // get the sheets from the given Format final String styleSheet = "fo-styles/" + form.getExportFormat() + "/timesheet-template-fo.xsl"; final String xmlData = "fo-styles/" + form.getExportFormat() + "/timesheets2pdf.xml"; // get the formatter for the different export formats final Formatter formatter = formatterFactory.getFormatter(form.getExportFormat()); final Integer taskId = filter.getTaskId(); final Map<String, Object> data = formatter.getData(timeSheets, taskId, getRequest(), getResponse(), filter); // render the PDF with fop final byte[] content = pdfRenderer.render(styleSheet, xmlData, data); DownloadUtils.setDownloadTarget(content, filename); } protected void exportExcel() { refresh(); final List<TimesheetDO> timeSheets = getList(); if (timeSheets == null || timeSheets.size() == 0) { // Nothing to export. form.addError("validation.error.nothingToExport"); return; } final String filename = "ProjectForge-TimesheetExport_" + DateHelper.getDateAsFilenameSuffix(new Date()) + ".xls"; final byte[] xls = timesheetExport.export(timeSheets); if (xls == null || xls.length == 0) { log.error("Oups, xls has zero size. Filename: " + filename); return; } DownloadUtils.setDownloadTarget(xls, filename); } /** * Avoid LazyInitializationException user.fullname. * @see org.projectforge.web.wicket.AbstractListPage#createSortableDataProvider(java.lang.String, boolean) */ @SuppressWarnings("serial") @Override protected ISortableDataProvider<TimesheetDO, String> createSortableDataProvider(final SortParam<String> sortParam) { this.listPageSortableDataProvider = new MyListPageSortableDataProvider<TimesheetDO>(sortParam, null, this) { @Override protected Comparator<TimesheetDO> getComparator(final SortParam<String> sortParam, final SortParam<String> secondSortParam) { final String sortProperty = sortParam != null ? sortParam.getProperty() : null; final boolean ascending = sortParam != null ? sortParam.isAscending() : true; final String secondSortProperty = secondSortParam != null ? secondSortParam.getProperty() : null; final boolean secondAscending = secondSortParam != null ? secondSortParam.isAscending() : true; return new MyBeanComparator<TimesheetDO>(sortProperty, ascending, secondSortProperty, secondAscending) { @Override public int compare(final TimesheetDO t1, final TimesheetDO t2) { if ("user.fullname".equals(sortProperty) == true) { PFUserDO user = t1.getUser(); if (user != null && Hibernate.isInitialized(user) == false) { t1.setUser(userGroupCache.getUser(user.getId())); } user = t2.getUser(); if (user != null && Hibernate.isInitialized(user) == false) { t2.setUser(userGroupCache.getUser(user.getId())); } } else if ("task.title".equals(sortProperty) == true) { TaskDO task = t1.getTask(); if (task != null && Hibernate.isInitialized(task) == false) { t1.setTask(taskTree.getTaskById(task.getId())); } task = t2.getTask(); if (task != null && Hibernate.isInitialized(task) == false) { t2.setTask(taskTree.getTaskById(task.getId())); } } return super.compare(t1, t2); } }; } }; return this.listPageSortableDataProvider; } /** * @see org.projectforge.web.wicket.AbstractListPage#getBookmarkableInitialProperties() */ @Override protected String[] getBookmarkableInitialProperties() { return MY_BOOKMARKABLE_INITIAL_PROPERTIES; } }