Add total row to monthly timesheets

Create a new class MonthlyTimesheetRow to manage this kind of special like total
or capacity in the future.

FEA: ItEr76S28UserDashboard
This commit is contained in:
Manuel Rego Casasnovas 2012-05-29 11:44:39 +02:00
parent 79eb53d6f0
commit 346214c2ce
5 changed files with 208 additions and 16 deletions

View file

@ -3,7 +3,7 @@
* *
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia * Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L. * Copyright (C) 2010-2012 Igalia, S.L.
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by * it under the terms of the GNU Affero General Public License as published by
@ -39,6 +39,7 @@ import org.libreplan.business.labels.entities.Label;
import org.libreplan.business.labels.entities.LabelType; import org.libreplan.business.labels.entities.LabelType;
import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workreports.daos.IWorkReportDAO; import org.libreplan.business.workreports.daos.IWorkReportDAO;
import org.libreplan.business.workreports.valueobjects.DescriptionField; import org.libreplan.business.workreports.valueobjects.DescriptionField;
import org.libreplan.business.workreports.valueobjects.DescriptionValue; import org.libreplan.business.workreports.valueobjects.DescriptionValue;
@ -46,6 +47,7 @@ import org.libreplan.business.workreports.valueobjects.DescriptionValue;
/** /**
* @author Diego Pino García <dpino@igalia.com> * @author Diego Pino García <dpino@igalia.com>
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com> * @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*/ */
public class WorkReport extends IntegrationEntity implements public class WorkReport extends IntegrationEntity implements
IWorkReportsElements { IWorkReportsElements {
@ -455,4 +457,12 @@ public class WorkReport extends IntegrationEntity implements
return lastWorkReportLineSequenceCode; return lastWorkReportLineSequenceCode;
} }
public EffortDuration getTotalEffortDuration() {
EffortDuration result = EffortDuration.zero();
for (WorkReportLine line : workReportLines) {
result = result.plus(line.getEffort());
}
return result;
}
} }

View file

@ -82,4 +82,17 @@ public interface IMonthlyTimesheetModel {
*/ */
EffortDuration getEffortDuration(OrderElement orderElement); EffortDuration getEffortDuration(OrderElement orderElement);
/**
* Returns the {@link EffortDuration} for all the {@link OrderElement
* OrderElements} in the current monthly timesheet in the specified
* <code>date</code>.
*/
EffortDuration getEffortDuration(LocalDate date);
/**
* Returns the total {@link EffortDuration} for the currently monthly
* timesheet.
*/
EffortDuration getTotalEffortDuration();
} }

View file

@ -22,6 +22,7 @@ package org.libreplan.web.users.dashboard;
import static org.libreplan.web.I18nHelper._; import static org.libreplan.web.I18nHelper._;
import static org.libreplan.web.planner.tabs.MultipleTabsPlannerController.BREADCRUMBS_SEPARATOR; import static org.libreplan.web.planner.tabs.MultipleTabsPlannerController.BREADCRUMBS_SEPARATOR;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
@ -30,10 +31,12 @@ import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.web.common.Util; import org.libreplan.web.common.Util;
import org.libreplan.web.common.entrypoints.IURLHandlerRegistry; import org.libreplan.web.common.entrypoints.IURLHandlerRegistry;
import org.libreplan.web.users.services.CustomTargetUrlResolver; import org.libreplan.web.users.services.CustomTargetUrlResolver;
import org.springframework.util.Assert;
import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Cell;
import org.zkoss.zul.Column; import org.zkoss.zul.Column;
import org.zkoss.zul.Columns; import org.zkoss.zul.Columns;
import org.zkoss.zul.Image; import org.zkoss.zul.Image;
@ -52,6 +55,8 @@ import org.zkoss.zul.api.Grid;
public class MonthlyTimesheetController extends GenericForwardComposer public class MonthlyTimesheetController extends GenericForwardComposer
implements IMonthlyTimesheetController { implements IMonthlyTimesheetController {
private final static String EFFORT_DURATION_TEXTBOX_WIDTH = "30px";
private IMonthlyTimesheetModel monthlyTimesheetModel; private IMonthlyTimesheetModel monthlyTimesheetModel;
private IURLHandlerRegistry URLHandlerRegistry; private IURLHandlerRegistry URLHandlerRegistry;
@ -60,13 +65,42 @@ public class MonthlyTimesheetController extends GenericForwardComposer
private Columns columns; private Columns columns;
private RowRenderer orderElementsRenderer = new RowRenderer() { private RowRenderer rowRenderer = new RowRenderer() {
private LocalDate start;
private LocalDate end;
@Override @Override
public void render(Row row, Object data) throws Exception { public void render(Row row, Object data) throws Exception {
OrderElement orderElement = (OrderElement) data; MonthlyTimesheetRow monthlyTimesheetRow = (MonthlyTimesheetRow) data;
row.setValue(orderElement);
initMonthlyTimesheetDates();
switch (monthlyTimesheetRow.getType()) {
case ORDER_ELEMENT:
renderOrderElementRow(row,
monthlyTimesheetRow.getOrderElemement());
break;
case CAPACITY:
// TODO
break;
case TOTAL:
renderTotalRow(row);
break;
default:
throw new IllegalStateException(
"Unknown MonthlyTimesheetRow type: "
+ monthlyTimesheetRow.getType());
}
}
private void initMonthlyTimesheetDates() {
LocalDate date = monthlyTimesheetModel.getDate();
start = date.dayOfMonth().withMinimumValue();
end = date.dayOfMonth().withMaximumValue();
}
private void renderOrderElementRow(Row row, OrderElement orderElement) {
Util.appendLabel(row, orderElement.getOrder().getName()); Util.appendLabel(row, orderElement.getOrder().getName());
Util.appendLabel(row, orderElement.getName()); Util.appendLabel(row, orderElement.getName());
@ -77,17 +111,12 @@ public class MonthlyTimesheetController extends GenericForwardComposer
private void appendInputsForDays(Row row, private void appendInputsForDays(Row row,
final OrderElement orderElement) { final OrderElement orderElement) {
LocalDate date = monthlyTimesheetModel.getDate();
LocalDate start = date.dayOfMonth().withMinimumValue();
LocalDate end = date.dayOfMonth().withMaximumValue();
for (LocalDate day = start; day.compareTo(end) <= 0; day = day for (LocalDate day = start; day.compareTo(end) <= 0; day = day
.plusDays(1)) { .plusDays(1)) {
final LocalDate textboxDate = day; final LocalDate textboxDate = day;
final Textbox textbox = new Textbox(); final Textbox textbox = new Textbox();
textbox.setWidth("30px"); textbox.setWidth(EFFORT_DURATION_TEXTBOX_WIDTH);
Util.bind(textbox, new Util.Getter<String>() { Util.bind(textbox, new Util.Getter<String>() {
@Override @Override
@ -108,7 +137,14 @@ public class MonthlyTimesheetController extends GenericForwardComposer
} }
monthlyTimesheetModel.setEffortDuration(orderElement, monthlyTimesheetModel.setEffortDuration(orderElement,
textboxDate, effortDuration); textboxDate, effortDuration);
updateTotals(orderElement, textboxDate);
}
private void updateTotals(OrderElement orderElement,
LocalDate date) {
updateTotalColumn(orderElement); updateTotalColumn(orderElement);
updateTotalRow(date);
updateTotalColumn();
} }
}); });
@ -120,6 +156,7 @@ public class MonthlyTimesheetController extends GenericForwardComposer
private void appendTotalColumn(Row row, final OrderElement orderElement) { private void appendTotalColumn(Row row, final OrderElement orderElement) {
Textbox textbox = new Textbox(); Textbox textbox = new Textbox();
textbox.setWidth(EFFORT_DURATION_TEXTBOX_WIDTH);
textbox.setId(getTotalColumnTextboxId(orderElement)); textbox.setId(getTotalColumnTextboxId(orderElement));
textbox.setDisabled(true); textbox.setDisabled(true);
row.appendChild(textbox); row.appendChild(textbox);
@ -137,6 +174,65 @@ public class MonthlyTimesheetController extends GenericForwardComposer
orderElement).toFormattedString()); orderElement).toFormattedString());
} }
private void renderTotalRow(Row row) {
appendTotalLabel(row);
appendTotalForDays(row);
appendTotalColumn(row);
}
private void appendTotalLabel(Row row) {
Label label = new Label(_("Total"));
Cell cell = new Cell();
cell.setColspan(2);
cell.appendChild(label);
row.appendChild(cell);
}
private void appendTotalForDays(Row row) {
for (LocalDate day = start; day.compareTo(end) <= 0; day = day
.plusDays(1)) {
Textbox textbox = new Textbox();
textbox.setWidth(EFFORT_DURATION_TEXTBOX_WIDTH);
textbox.setId(getTotalRowTextboxId(day));
textbox.setDisabled(true);
row.appendChild(textbox);
updateTotalRow(day);
}
}
private String getTotalRowTextboxId(LocalDate date) {
return "textbox-total-row-" + date;
}
private void updateTotalRow(LocalDate date) {
Textbox textbox = (Textbox) timesheet
.getFellow(getTotalRowTextboxId(date));
textbox.setValue(monthlyTimesheetModel.getEffortDuration(date)
.toFormattedString());
}
private void appendTotalColumn(Row row) {
Textbox textbox = new Textbox();
textbox.setWidth(EFFORT_DURATION_TEXTBOX_WIDTH);
textbox.setId(getTotalTextboxId());
textbox.setDisabled(true);
row.appendChild(textbox);
updateTotalColumn();
}
private String getTotalTextboxId() {
return "textbox-total";
}
private void updateTotalColumn() {
Textbox textbox = (Textbox) timesheet
.getFellow(getTotalTextboxId());
textbox.setValue(monthlyTimesheetModel.getTotalEffortDuration()
.toFormattedString());
}
}; };
@Override @Override
@ -210,12 +306,16 @@ public class MonthlyTimesheetController extends GenericForwardComposer
return monthlyTimesheetModel.getWorker().getShortDescription(); return monthlyTimesheetModel.getWorker().getShortDescription();
} }
public List<OrderElement> getOrderElements() { public List<MonthlyTimesheetRow> getRows() {
return monthlyTimesheetModel.getOrderElements(); List<MonthlyTimesheetRow> result = MonthlyTimesheetRow
.wrap(monthlyTimesheetModel
.getOrderElements());
result.add(MonthlyTimesheetRow.createTotalRow());
return result;
} }
public RowRenderer getOrderElementsRenderer() { public RowRenderer getRowRenderer() {
return orderElementsRenderer; return rowRenderer;
} }
public void save() { public void save() {
@ -232,3 +332,56 @@ public class MonthlyTimesheetController extends GenericForwardComposer
} }
} }
/**
* Simple class to represent the the rows in the monthly timesheet grid.<br />
*
* This is used to mark the special rows like capacity and total.
*/
class MonthlyTimesheetRow {
enum MonthlyTimesheetRowType {
ORDER_ELEMENT, CAPACITY, TOTAL
};
private MonthlyTimesheetRowType type;
private OrderElement orderElemement;
public static MonthlyTimesheetRow createOrderElementRow(
OrderElement orderElemement) {
MonthlyTimesheetRow row = new MonthlyTimesheetRow(
MonthlyTimesheetRowType.ORDER_ELEMENT);
Assert.notNull(orderElemement);
row.orderElemement = orderElemement;
return row;
}
public static MonthlyTimesheetRow createCapacityRow() {
return new MonthlyTimesheetRow(MonthlyTimesheetRowType.CAPACITY);
}
public static MonthlyTimesheetRow createTotalRow() {
return new MonthlyTimesheetRow(MonthlyTimesheetRowType.TOTAL);
}
public static List<MonthlyTimesheetRow> wrap(
List<OrderElement> orderElements) {
List<MonthlyTimesheetRow> result = new ArrayList<MonthlyTimesheetRow>();
for (OrderElement each : orderElements) {
result.add(createOrderElementRow(each));
}
return result;
}
private MonthlyTimesheetRow(MonthlyTimesheetRowType type) {
this.type = type;
}
public MonthlyTimesheetRowType getType() {
return type;
}
public OrderElement getOrderElemement() {
return orderElemement;
}
}

View file

@ -244,4 +244,20 @@ public class MonthlyTimesheetModel implements IMonthlyTimesheetModel {
return result; return result;
} }
@Override
public EffortDuration getEffortDuration(LocalDate date) {
EffortDuration result = EffortDuration.zero();
for (WorkReportLine line : workReport.getWorkReportLines()) {
if (LocalDate.fromDateFields(line.getDate()).equals(date)) {
result = result.plus(line.getEffort());
}
}
return result;
}
@Override
public EffortDuration getTotalEffortDuration() {
return workReport.getTotalEffortDuration();
}
} }

View file

@ -53,8 +53,8 @@
<groupbox style="margin-top: 5px" closable="false"> <groupbox style="margin-top: 5px" closable="false">
<caption label="${i18n:_('Time tracking')}" /> <caption label="${i18n:_('Time tracking')}" />
<grid id="timesheet" <grid id="timesheet"
model="@{controller.orderElements}" model="@{controller.rows}"
rowRenderer="@{controller.orderElementsRenderer}" /> rowRenderer="@{controller.rowRenderer}" />
</groupbox> </groupbox>
<button onClick="controller.save();" <button onClick="controller.save();"