Merge branch 'adapt-planning-according-timesheets'

This commit is contained in:
Manuel Rego Casasnovas 2012-11-15 12:09:10 +01:00
commit b218f380a7
63 changed files with 1405 additions and 157 deletions

View file

@ -509,9 +509,11 @@ public class Planner extends HtmlMacroComponent {
} }
for (CommandContextualized<?> c : contextualizedGlobalCommands) { for (CommandContextualized<?> c : contextualizedGlobalCommands) {
// Comparison through icon as name is internationalized // Comparison through icon as name is internationalized
if (c.getCommand().getImage() if (c.getCommand().isPlannerCommand()) {
.equals("/common/img/ico_reassign.png")) { // FIXME Avoid hard-coding the number of planner commands
if (plannerToolbar.getChildren().isEmpty()) { // At this moment we have 2 planner commands: reassign and adapt
// planning
if (plannerToolbar.getChildren().size() < 2) {
plannerToolbar.appendChild(c.toButton()); plannerToolbar.appendChild(c.toButton());
} }
} else { } else {
@ -942,4 +944,18 @@ public class Planner extends HtmlMacroComponent {
} }
} }
public TaskComponent getTaskComponentRelatedTo(
org.zkoss.ganttz.data.Task task) {
TaskList taskList = getTaskList();
if (taskList != null) {
for (TaskComponent each : taskList.getTaskComponents()) {
if (each.getTask().equals(task)) {
return each;
}
}
}
return null;
}
} }

View file

@ -24,11 +24,14 @@ package org.zkoss.ganttz;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.io.IOException; import java.io.IOException;
import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Duration;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.zkoss.ganttz.adapters.IDisabilityConfiguration; import org.zkoss.ganttz.adapters.IDisabilityConfiguration;
import org.zkoss.ganttz.data.GanttDate; import org.zkoss.ganttz.data.GanttDate;
@ -517,8 +520,34 @@ public class TaskComponent extends Div implements AfterCompose {
this.task.getHoursAdvanceEndDate()) + "px"; this.task.getHoursAdvanceEndDate()) + "px";
response(null, new AuInvoke(this, "resizeCompletionAdvance", response(null, new AuInvoke(this, "resizeCompletionAdvance",
widthHoursAdvancePercentage)); widthHoursAdvancePercentage));
Date firstTimesheetDate = task.getFirstTimesheetDate();
Date lastTimesheetDate = task.getLastTimesheetDate();
if (firstTimesheetDate != null && lastTimesheetDate != null) {
Duration firstDuration = Days.daysBetween(
task.getBeginDateAsLocalDate(),
LocalDate.fromDateFields(firstTimesheetDate))
.toStandardDuration();
int pixelsFirst = getMapper().toPixels(firstDuration);
String positionFirst = pixelsFirst + "px";
Duration lastDuration = Days
.daysBetween(
task.getBeginDateAsLocalDate(),
LocalDate.fromDateFields(lastTimesheetDate)
.plusDays(1)).toStandardDuration();
int pixelsLast = getMapper().toPixels(lastDuration);
String positionLast = pixelsLast + "px";
response(null, new AuInvoke(this, "showTimsheetDateMarks",
positionFirst, positionLast));
} else {
response(null, new AuInvoke(this, "hideTimsheetDateMarks"));
}
} else { } else {
response(null, new AuInvoke(this, "resizeCompletionAdvance", "0px")); response(null, new AuInvoke(this, "resizeCompletionAdvance", "0px"));
response(null, new AuInvoke(this, "hideTimsheetDateMarks"));
} }
} }

View file

@ -86,6 +86,11 @@ public class PlannerConfiguration<T> implements IDisabilityConfiguration {
return false; return false;
} }
@Override
public boolean isPlannerCommand() {
return false;
}
} }
private static class NullCommandOnTask<T> implements ICommandOnTask<T> { private static class NullCommandOnTask<T> implements ICommandOnTask<T> {

View file

@ -308,4 +308,19 @@ public class DefaultFundamentalProperties implements ITaskFundamentalProperties
return false; return false;
} }
@Override
public boolean isUpdatedFromTimesheets() {
return false;
}
@Override
public Date getFirstTimesheetDate() {
return null;
}
@Override
public Date getLastTimesheetDate() {
return null;
}
} }

View file

@ -125,4 +125,10 @@ public interface ITaskFundamentalProperties {
public boolean isRoot(); public boolean isRoot();
boolean isUpdatedFromTimesheets();
Date getFirstTimesheetDate();
Date getLastTimesheetDate();
} }

View file

@ -548,6 +548,21 @@ public abstract class Task implements ITaskFundamentalProperties {
getBeginDate()); getBeginDate());
} }
@Override
public boolean isUpdatedFromTimesheets() {
return fundamentalProperties.isUpdatedFromTimesheets();
}
@Override
public Date getFirstTimesheetDate() {
return fundamentalProperties.getFirstTimesheetDate();
}
@Override
public Date getLastTimesheetDate() {
return fundamentalProperties.getLastTimesheetDate();
}
public void firePropertyChangeForTaskDates() { public void firePropertyChangeForTaskDates() {
fundamentalPropertiesListeners.firePropertyChange("beginDate", null, fundamentalPropertiesListeners.firePropertyChange("beginDate", null,
getBeginDate()); getBeginDate());

View file

@ -56,7 +56,7 @@ public class TaskLeaf extends Task {
@Override @Override
public boolean canBeExplicitlyMoved() { public boolean canBeExplicitlyMoved() {
return !(isSubcontracted() || isLimitingAndHasDayAssignments() return !(isSubcontracted() || isLimitingAndHasDayAssignments()
|| hasConsolidations() || isManualAnyAllocation()); || hasConsolidations() || isManualAnyAllocation() || isUpdatedFromTimesheets());
} }
} }

View file

@ -39,4 +39,10 @@ public interface ICommand<T> {
boolean isDisabled(); boolean isDisabled();
/**
* Describes if a command is for the planner toolbar. Otherwise it'll be
* inserted in the common toolbar.
*/
boolean isPlannerCommand();
} }

View file

@ -221,6 +221,18 @@ ganttz.TaskComponent = zk.$extends(zul.Widget, {
resizeCompletionAdvance : function(width){ resizeCompletionAdvance : function(width){
jq('#' + this.uuid + ' .completion:first').css('width', width); jq('#' + this.uuid + ' .completion:first').css('width', width);
}, },
showTimsheetDateMarks : function(positionFirst, postionLast) {
var firstTimesheetDateMark = jq('#' + this.uuid + ' .first-timesheet-date');
var lastTimesheetDateMark = jq('#' + this.uuid + ' .last-timesheet-date');
firstTimesheetDateMark.css('left', positionFirst);
lastTimesheetDateMark.css('left', postionLast);
firstTimesheetDateMark.show();
lastTimesheetDateMark.show();
},
hideTimsheetDateMarks : function() {
jq('#' + this.uuid + ' .first-timesheet-date').hide();
jq('#' + this.uuid + ' .last-timesheet-date').hide();
},
resizeCompletion2Advance : function(width){ resizeCompletion2Advance : function(width){
jq('#' + this.uuid + ' .completion2:first').css('width', width); jq('#' + this.uuid + ' .completion2:first').css('width', width);
}, },

View file

@ -14,6 +14,8 @@ function(out){
out.push('<div class="completionMoneyCostBar"></div>'); out.push('<div class="completionMoneyCostBar"></div>');
out.push('<div class="completion"></div>'); out.push('<div class="completion"></div>');
out.push('<div class="completion2"></div>'); out.push('<div class="completion2"></div>');
out.push('<div class="timesheet-date-mark first-timesheet-date">|</div>');
out.push('<div class="timesheet-date-mark last-timesheet-date">|</div>');
out.push('<div id="tasktooltip', this.uuid,'" class="task_tooltip">', out.push('<div id="tasktooltip', this.uuid,'" class="task_tooltip">',
this.getTooltipText(), this.getTooltipText(),

View file

@ -36,16 +36,26 @@ public enum PredefinedAdvancedTypes {
UNITS("units", new BigDecimal(Integer.MAX_VALUE), UNITS("units", new BigDecimal(Integer.MAX_VALUE),
new BigDecimal(1), false, false), new BigDecimal(1), false, false),
SUBCONTRACTOR("subcontractor", SUBCONTRACTOR("subcontractor",
new BigDecimal(100), new BigDecimal(0.01), true, false); new BigDecimal(100), new BigDecimal(0.01), true, false),
TIMESHEETS("timesheets",
new BigDecimal(100), new BigDecimal(0.01), true, false, true);
private PredefinedAdvancedTypes(String name, BigDecimal defaultMaxValue, private PredefinedAdvancedTypes(String name, BigDecimal defaultMaxValue,
BigDecimal precision, boolean percentage, boolean qualityForm) { BigDecimal precision, boolean percentage, boolean qualityForm) {
this(name, defaultMaxValue, precision, percentage, qualityForm, false);
}
private PredefinedAdvancedTypes(String name, BigDecimal defaultMaxValue,
BigDecimal precision, boolean percentage, boolean qualityForm,
boolean readOnly) {
this.name = name; this.name = name;
this.defaultMaxValue = defaultMaxValue.setScale(4, this.defaultMaxValue = defaultMaxValue.setScale(4,
BigDecimal.ROUND_HALF_UP); BigDecimal.ROUND_HALF_UP);
this.unitPrecision = precision.setScale(4, BigDecimal.ROUND_HALF_UP); this.unitPrecision = precision.setScale(4, BigDecimal.ROUND_HALF_UP);
this.percentage = percentage; this.percentage = percentage;
this.qualityForm = qualityForm; this.qualityForm = qualityForm;
this.readOnly = readOnly;
} }
private final String name; private final String name;
@ -58,9 +68,13 @@ public enum PredefinedAdvancedTypes {
private final boolean qualityForm; private final boolean qualityForm;
private final boolean readOnly;
public AdvanceType createType() { public AdvanceType createType() {
return AdvanceType.create(name, defaultMaxValue, false, unitPrecision, AdvanceType advanceType = AdvanceType.create(name, defaultMaxValue,
true, percentage, qualityForm); false, unitPrecision, true, percentage, qualityForm);
advanceType.setReadOnly(readOnly);
return advanceType;
} }
public String getTypeName() { public String getTypeName() {

View file

@ -86,6 +86,8 @@ public class AdvanceType extends BaseEntity implements IHumanIdentifiable{
private IAdvanceTypeDAO avanceTypeDAO = Registry.getAdvanceTypeDao(); private IAdvanceTypeDAO avanceTypeDAO = Registry.getAdvanceTypeDao();
private boolean readOnly = false;
/** /**
* Constructor for hibernate. Do not use! * Constructor for hibernate. Do not use!
*/ */
@ -271,4 +273,12 @@ public class AdvanceType extends BaseEntity implements IHumanIdentifiable{
return true; return true;
} }
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
public boolean isReadOnly() {
return readOnly;
}
} }

View file

@ -263,4 +263,9 @@ public class DirectAdvanceAssignment extends AdvanceAssignment {
return !nonCalculatedConsolidations.isEmpty(); return !nonCalculatedConsolidations.isEmpty();
} }
public void resetAdvanceMeasurements(AdvanceMeasurement advanceMeasurement) {
advanceMeasurements.clear();
addAdvanceMeasurements(advanceMeasurement);
}
} }

View file

@ -0,0 +1,50 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 Igalia, S.L.
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.business.common;
import java.util.Collection;
/**
* Utilities class. <br />
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*/
public class Util {
public static boolean equals(BaseEntity entity1, BaseEntity entity2) {
if (entity1 == null || entity2 == null) {
return false;
}
if (entity1.getId() == null || entity2.getId() == null) {
return false;
}
return entity1.getId().equals(entity2.getId());
}
public static boolean contains(Collection<? extends BaseEntity> collection,
BaseEntity entity) {
for (BaseEntity each : collection) {
if (each.getId().equals(entity.getId())) {
return true;
}
}
return false;
}
}

View file

@ -74,4 +74,26 @@ public interface ISumChargedEffortDAO extends
*/ */
void recalculateSumChargedEfforts(Long orderId); void recalculateSumChargedEfforts(Long orderId);
/**
* Returns a {@link Set} of {@link OrderElement OrderElements} affected by
* any change taking into account the lines in the report and the ones to be
* removed.
*
* Usually you call this method to get the set before saving the work
* report. After saving the work report you call
* {@link ISumChargedEffortDAO#recalculateTimesheetData(Set)} with the
* result of this method.
*
* You can pass <code>null</code> as param if you only have one of the sets.
*/
Set<OrderElement> getOrderElementsToRecalculateTimsheetDates(
Set<WorkReportLine> workReportLines,
Set<WorkReportLine> deletedWorkReportLinesSet);
/**
* Recalulates the first and last timesheets dates for each
* {@link OrderElement} in the {@link Set}.
*/
void recalculateTimesheetData(Set<OrderElement> orderElements);
} }

View file

@ -19,7 +19,11 @@
package org.libreplan.business.orders.daos; package org.libreplan.business.orders.daos;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -66,6 +70,9 @@ public class SumChargedEffortDAO extends
@Autowired @Autowired
private IOrderDAO orderDAO; private IOrderDAO orderDAO;
@Autowired
private IOrderElementDAO orderElementDAO;
private Map<OrderElement, SumChargedEffort> mapSumChargedEfforts; private Map<OrderElement, SumChargedEffort> mapSumChargedEfforts;
@Override @Override
@ -114,6 +121,7 @@ public class SumChargedEffortDAO extends
forceLoadParents(parent); forceLoadParents(parent);
} }
} }
}); });
previousEffort = previous.getFirst(); previousEffort = previous.getFirst();
@ -149,9 +157,6 @@ public class SumChargedEffortDAO extends
private void addDirectChargedEffort(OrderElement orderElement, private void addDirectChargedEffort(OrderElement orderElement,
EffortDuration effort) { EffortDuration effort) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement); SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
sumChargedEffort.addDirectChargedEffort(effort); sumChargedEffort.addDirectChargedEffort(effort);
save(sumChargedEffort); save(sumChargedEffort);
@ -163,9 +168,6 @@ public class SumChargedEffortDAO extends
EffortDuration effort) { EffortDuration effort) {
if (orderElement != null) { if (orderElement != null) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement); SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
sumChargedEffort.addIndirectChargedEffort(effort); sumChargedEffort.addIndirectChargedEffort(effort);
save(sumChargedEffort); save(sumChargedEffort);
@ -231,6 +233,9 @@ public class SumChargedEffortDAO extends
.get(orderElement); .get(orderElement);
if (sumChargedEffort == null) { if (sumChargedEffort == null) {
sumChargedEffort = findByOrderElement(orderElement); sumChargedEffort = findByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
mapSumChargedEfforts.put(orderElement, sumChargedEffort); mapSumChargedEfforts.put(orderElement, sumChargedEffort);
} }
return sumChargedEffort; return sumChargedEffort;
@ -251,6 +256,7 @@ public class SumChargedEffortDAO extends
resetMapSumChargedEfforts(); resetMapSumChargedEfforts();
resetSumChargedEffort(order); resetSumChargedEffort(order);
calculateDirectChargedEffort(order); calculateDirectChargedEffort(order);
calculateTimesheetData(order);
} catch (InstanceNotFoundException e) { } catch (InstanceNotFoundException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -258,9 +264,6 @@ public class SumChargedEffortDAO extends
private void resetSumChargedEffort(OrderElement orderElement) { private void resetSumChargedEffort(OrderElement orderElement) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement); SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
sumChargedEffort.reset(); sumChargedEffort.reset();
for (OrderElement each : orderElement.getChildren()) { for (OrderElement each : orderElement.getChildren()) {
@ -281,4 +284,159 @@ public class SumChargedEffortDAO extends
addDirectChargedEffort(orderElement, effort); addDirectChargedEffort(orderElement, effort);
} }
private void calculateTimesheetData(OrderElement orderElement) {
calculateTimesheetDatesAndChildren(orderElement);
calculateFinishedTimesheetsAndChildren(orderElement);
}
private Pair<Date, Date> calculateTimesheetDatesAndChildren(
OrderElement orderElement) {
Pair<Date, Date> minMax = workReportLineDAO
.findMinAndMaxDatesByOrderElement(orderElement);
Set<Date> minDates = new HashSet<Date>();
Set<Date> maxDates = new HashSet<Date>();
addIfNotNull(minDates, minMax.getFirst());
addIfNotNull(maxDates, minMax.getSecond());
for (OrderElement child : orderElement.getChildren()) {
Pair<Date, Date> minMaxChild = calculateTimesheetDatesAndChildren(child);
addIfNotNull(minDates, minMaxChild.getFirst());
addIfNotNull(maxDates, minMaxChild.getSecond());
}
Pair<Date, Date> result = Pair.create(
minDates.isEmpty() ? null : Collections.min(minDates),
maxDates.isEmpty() ? null : Collections.max(maxDates));
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
sumChargedEffort.setTimesheetDates(result.getFirst(),
result.getSecond());
save(sumChargedEffort);
return result;
}
private void addIfNotNull(Collection<Date> list, Date date) {
if (date != null) {
list.add(date);
}
}
private void calculateFinishedTimesheetsAndChildren(
OrderElement orderElement) {
calculateFinishedTimesheets(orderElement);
for (OrderElement child : orderElement.getChildren()) {
calculateFinishedTimesheetsAndChildren(child);
}
}
private void calculateFinishedTimesheets(OrderElement orderElement) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
sumChargedEffort.setFinishedTimesheets(workReportLineDAO
.isFinished(orderElement));
save(sumChargedEffort);
}
@Override
@Transactional(readOnly = true)
public Set<OrderElement> getOrderElementsToRecalculateTimsheetDates(
Set<WorkReportLine> workReportLines,
Set<WorkReportLine> deletedWorkReportLines) {
Set<OrderElement> orderElements = new HashSet<OrderElement>();
if (workReportLines != null) {
for (final WorkReportLine workReportLine : workReportLines) {
if (!workReportLine.isNewObject()) {
OrderElement previousOrderElement = transactionService
.runOnAnotherTransaction(new IOnTransaction<OrderElement>() {
@Override
public OrderElement execute() {
try {
WorkReportLine line = workReportLineDAO
.find(workReportLine.getId());
return line.getOrderElement();
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
}
});
orderElements.add(previousOrderElement);
}
orderElements.add(workReportLine.getOrderElement());
}
}
if (deletedWorkReportLines != null) {
for (WorkReportLine workReportLine : deletedWorkReportLines) {
if (workReportLine.isNewObject()) {
// If the line hasn't been saved, we don't take it into
// account
continue;
}
// Refresh data from database, because of changes not saved are
// not
// useful for the following operations
sessionFactory.getCurrentSession().refresh(workReportLine);
orderElements.add(workReportLine.getOrderElement());
}
}
return orderElements;
}
@Override
@Transactional
public void recalculateTimesheetData(Set<OrderElement> orderElements) {
try {
for (OrderElement orderElement : orderElements) {
saveTimesheetDatesRecursively(orderElementDAO.find(orderElement
.getId()));
calculateFinishedTimesheets(orderElementDAO.find(orderElement
.getId()));
}
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
}
private void saveTimesheetDatesRecursively(OrderElement orderElement) {
if (orderElement != null) {
saveTimesheetDates(orderElement);
saveTimesheetDatesRecursively(orderElement.getParent());
}
}
private void saveTimesheetDates(OrderElement orderElement) {
Pair<Date, Date> minMax = workReportLineDAO
.findMinAndMaxDatesByOrderElement(orderElement);
Set<Date> minDates = new HashSet<Date>();
Set<Date> maxDates = new HashSet<Date>();
addIfNotNull(minDates, minMax.getFirst());
addIfNotNull(maxDates, minMax.getSecond());
for (OrderElement child : orderElement.getChildren()) {
SumChargedEffort childSumChargedEffort = getByOrderElement(child);
addIfNotNull(minDates,
childSumChargedEffort.getFirstTimesheetDate());
addIfNotNull(maxDates, childSumChargedEffort.getLastTimesheetDate());
}
Pair<Date, Date> result = Pair.create(minDates.isEmpty() ? null
: Collections.min(minDates), maxDates.isEmpty() ? null
: Collections.max(maxDates));
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
sumChargedEffort.setTimesheetDates(result.getFirst(),
result.getSecond());
save(sumChargedEffort);
}
} }

View file

@ -1014,6 +1014,14 @@ public abstract class OrderElement extends IntegrationEntity implements
return getCurrentSchedulingData().getTaskSource(); return getCurrentSchedulingData().getTaskSource();
} }
public TaskElement getTaskElement() {
TaskSource taskSource = getTaskSource();
if (taskSource == null) {
return null;
}
return taskSource.getTask();
}
public Set<TaskElement> getTaskElements() { public Set<TaskElement> getTaskElements() {
if (getTaskSource() == null) { if (getTaskSource() == null) {
return Collections.emptySet(); return Collections.emptySet();
@ -1586,6 +1594,44 @@ public abstract class OrderElement extends IntegrationEntity implements
return false; return false;
} }
public boolean hasTimesheetsReportingHours() {
if (sumChargedEffort == null) {
return false;
}
return sumChargedEffort.getFirstTimesheetDate() != null;
}
public boolean isFinishedTimesheets() {
if (sumChargedEffort == null) {
return false;
}
return sumChargedEffort.isFinishedTimesheets();
}
@Override
public boolean isUpdatedFromTimesheets() {
TaskElement taskElement = getTaskElement();
if (taskElement == null) {
return false;
}
return taskElement.isUpdatedFromTimesheets();
}
public Date getFirstTimesheetDate() {
if (sumChargedEffort == null) {
return null;
}
return sumChargedEffort.getFirstTimesheetDate();
}
public Date getLastTimesheetDate() {
if (sumChargedEffort == null) {
return null;
}
return sumChargedEffort.getLastTimesheetDate();
}
public void detachFromParent() { public void detachFromParent() {
parent = null; parent = null;
} }

View file

@ -158,6 +158,11 @@ public class OrderLineGroup extends OrderElement implements
return false; return false;
} }
@Override
public boolean isUpdatedFromTimesheets() {
return getThis().isUpdatedFromTimesheets();
}
} }
public static OrderLineGroup create() { public static OrderLineGroup create() {

View file

@ -21,8 +21,12 @@
package org.libreplan.business.orders.entities; package org.libreplan.business.orders.entities;
import java.util.Date;
import org.apache.commons.lang.BooleanUtils;
import org.libreplan.business.common.BaseEntity; import org.libreplan.business.common.BaseEntity;
import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workreports.entities.WorkReportLine;
/** /**
* It represents the efforts charged to an {@link OrderElement}, avoiding the * It represents the efforts charged to an {@link OrderElement}, avoiding the
@ -39,6 +43,17 @@ public class SumChargedEffort extends BaseEntity {
private EffortDuration indirectChargedEffort = EffortDuration.zero(); private EffortDuration indirectChargedEffort = EffortDuration.zero();
private Date firstTimesheetDate;
private Date lastTimesheetDate;
/**
* Finished according to timesheets. If <code>true</code> it means that
* there's a {@link WorkReportLine} marking as finished this
* {@link OrderElement}.
*/
private Boolean finishedTimesheets = false;
protected SumChargedEffort() {} protected SumChargedEffort() {}
private SumChargedEffort(OrderElement orderElement) { private SumChargedEffort(OrderElement orderElement) {
@ -93,6 +108,38 @@ public class SumChargedEffort extends BaseEntity {
public void reset() { public void reset() {
directChargedEffort = EffortDuration.zero(); directChargedEffort = EffortDuration.zero();
indirectChargedEffort = EffortDuration.zero(); indirectChargedEffort = EffortDuration.zero();
firstTimesheetDate = null;
lastTimesheetDate = null;
}
public Date getFirstTimesheetDate() {
return firstTimesheetDate;
}
public void setFirstTimesheetDate(Date firstTimesheetDate) {
this.firstTimesheetDate = firstTimesheetDate;
}
public Date getLastTimesheetDate() {
return lastTimesheetDate;
}
public void setLastTimesheetDate(Date lastTimesheetDate) {
this.lastTimesheetDate = lastTimesheetDate;
}
public void setTimesheetDates(Date firstTimesheetDate,
Date lastTimesheetDate) {
setFirstTimesheetDate(firstTimesheetDate);
setLastTimesheetDate(lastTimesheetDate);
}
public Boolean isFinishedTimesheets() {
return finishedTimesheets;
}
public void setFinishedTimesheets(Boolean finishedTimesheets) {
this.finishedTimesheets = BooleanUtils.isTrue(finishedTimesheets);
} }
} }

View file

@ -2253,4 +2253,17 @@ public abstract class ResourceAllocation<T extends DayAssignment> extends
intendedResourcesPerDay = getNonConsolidatedResourcePerDay(); intendedResourcesPerDay = getNonConsolidatedResourcePerDay();
} }
public void removeDayAssignmentsBeyondDate(LocalDate date) {
List<T> toRemove = new ArrayList<T>();
for (T t : getAssignments()) {
if (t.getDay().compareTo(date) >= 0) {
toRemove.add(t);
}
}
setOnDayAssignmentRemoval(new DetachDayAssignmentOnRemoval());
getDayAssignmentsState().removingAssignments(toRemove);
}
} }

View file

@ -37,6 +37,7 @@ import java.util.Set;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -182,6 +183,8 @@ public abstract class TaskElement extends BaseEntity {
private Boolean simplifiedAssignedStatusCalculationEnabled = false; private Boolean simplifiedAssignedStatusCalculationEnabled = false;
private Boolean updatedFromTimesheets = false;
public void initializeDatesIfNeeded() { public void initializeDatesIfNeeded() {
if (getIntraDayEndDate() == null || getIntraDayStartDate() == null) { if (getIntraDayEndDate() == null || getIntraDayStartDate() == null) {
initializeDates(); initializeDates();
@ -837,4 +840,12 @@ public abstract class TaskElement extends BaseEntity {
return result; return result;
} }
public Boolean isUpdatedFromTimesheets() {
return updatedFromTimesheets;
}
public void setUpdatedFromTimesheets(Boolean updatedFromTimesheets) {
this.updatedFromTimesheets = BooleanUtils.isTrue(updatedFromTimesheets);
}
} }

View file

@ -620,4 +620,9 @@ public abstract class OrderElementTemplate extends BaseEntity implements
public abstract boolean isOrderTemplate(); public abstract boolean isOrderTemplate();
@Override
public boolean isUpdatedFromTimesheets() {
return false;
}
} }

View file

@ -100,6 +100,11 @@ public class OrderLineGroupTemplate extends OrderElementTemplate implements
return false; return false;
} }
@Override
public boolean isUpdatedFromTimesheets() {
return false;
}
} }
public static OrderLineGroupTemplate createNew() { public static OrderLineGroupTemplate createNew() {

View file

@ -60,4 +60,6 @@ public interface ITreeNode<T extends ITreeNode<T>> {
*/ */
boolean isEmptyLeaf(); boolean isEmptyLeaf();
boolean isUpdatedFromTimesheets();
} }

View file

@ -28,6 +28,7 @@ import org.libreplan.business.common.daos.IIntegrationEntityDAO;
import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.reports.dtos.WorkReportLineDTO; import org.libreplan.business.reports.dtos.WorkReportLineDTO;
import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.util.Pair;
import org.libreplan.business.workreports.entities.WorkReport; import org.libreplan.business.workreports.entities.WorkReport;
import org.libreplan.business.workreports.entities.WorkReportLine; import org.libreplan.business.workreports.entities.WorkReportLine;
@ -65,6 +66,14 @@ public interface IWorkReportLineDAO extends
List<WorkReportLine> findByResourceFilteredByDateNotInWorkReport( List<WorkReportLine> findByResourceFilteredByDateNotInWorkReport(
Resource resource, Date start, Date end, WorkReport workReport); Resource resource, Date start, Date end, WorkReport workReport);
Pair<Date, Date> findMinAndMaxDatesByOrderElement(
OrderElement orderElement);
List<WorkReportLine> findByOrderElementNotInWorkReportAnotherTransaction(
OrderElement orderElement, WorkReport workReport);
Boolean isFinished(OrderElement orderElement);
List<WorkReportLine> findByOrderElementAndWorkReports( List<WorkReportLine> findByOrderElementAndWorkReports(
OrderElement orderElement, List<WorkReport> workReports); OrderElement orderElement, List<WorkReport> workReports);

View file

@ -34,11 +34,13 @@ import org.libreplan.business.common.daos.IntegrationEntityDAO;
import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.reports.dtos.WorkReportLineDTO; import org.libreplan.business.reports.dtos.WorkReportLineDTO;
import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.util.Pair;
import org.libreplan.business.workreports.entities.WorkReport; import org.libreplan.business.workreports.entities.WorkReport;
import org.libreplan.business.workreports.entities.WorkReportLine; import org.libreplan.business.workreports.entities.WorkReportLine;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
@ -146,6 +148,57 @@ public class WorkReportLineDAO extends IntegrationEntityDAO<WorkReportLine>
return criteria.list(); return criteria.list();
} }
@Override
public Pair<Date, Date> findMinAndMaxDatesByOrderElement(
OrderElement orderElement) {
String strQuery = "SELECT MIN(date) AS min, MAX(date) AS max "
+ "FROM WorkReportLine " + "WHERE orderElement = :orderElement";
Query query = getSession().createQuery(strQuery);
query.setParameter("orderElement", orderElement);
Object[] result = (Object[]) query.uniqueResult();
Date min = null;
Date max = null;
if (result != null) {
min = (Date) result[0];
max = (Date) result[1];
}
return Pair.create(min, max);
}
@Override
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
public List<WorkReportLine> findByOrderElementNotInWorkReportAnotherTransaction(
OrderElement orderElement, WorkReport workReport) {
return findByOrderElementNotInWorkReport(orderElement, workReport);
}
@SuppressWarnings("unchecked")
private List<WorkReportLine> findByOrderElementNotInWorkReport(
OrderElement orderElement, WorkReport workReport) {
Criteria criteria = getSession().createCriteria(WorkReportLine.class);
criteria.add(Restrictions.eq("orderElement", orderElement));
if (!workReport.isNewObject()) {
criteria.add(Restrictions.ne("workReport", workReport));
}
return (List<WorkReportLine>) criteria.list();
}
@Override
public Boolean isFinished(OrderElement orderElement) {
Criteria criteria = getSession().createCriteria(WorkReportLine.class);
criteria.add(Restrictions.eq("orderElement", orderElement));
criteria.add(Restrictions.eq("finished", true));
return criteria.uniqueResult() != null;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<WorkReportLine> findByOrderElementAndWorkReports( public List<WorkReportLine> findByOrderElementAndWorkReports(

View file

@ -36,6 +36,7 @@ import org.hibernate.validator.Valid;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.libreplan.business.common.IntegrationEntity; import org.libreplan.business.common.IntegrationEntity;
import org.libreplan.business.common.Registry; import org.libreplan.business.common.Registry;
import org.libreplan.business.common.Util;
import org.libreplan.business.common.entities.EntitySequence; import org.libreplan.business.common.entities.EntitySequence;
import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum;
import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.common.exceptions.InstanceNotFoundException;
@ -538,4 +539,20 @@ public class WorkReport extends IntegrationEntity implements
return false; return false;
} }
@AssertTrue(message = "the same task is marked as finished by more than one timesheet line")
public boolean checkConstraintSameOrderElementFinishedBySeveralWorkReportLines() {
Set<OrderElement> finishedOrderElements = new HashSet<OrderElement>();
for (WorkReportLine line : workReportLines) {
if (line.isFinished()) {
if (Util.contains(finishedOrderElements, line.getOrderElement())) {
return false;
}
finishedOrderElements.add(line.getOrderElement());
}
}
return true;
}
} }

View file

@ -23,6 +23,7 @@ package org.libreplan.business.workreports.entities;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@ -78,6 +79,8 @@ public class WorkReportLine extends IntegrationEntity implements Comparable,
private TypeOfWorkHours typeOfWorkHours; private TypeOfWorkHours typeOfWorkHours;
private Boolean finished = false;
/** /**
* Constructor for hibernate. Do not use! * Constructor for hibernate. Do not use!
*/ */
@ -552,4 +555,31 @@ public class WorkReportLine extends IntegrationEntity implements Comparable,
: false; : false;
} }
@NotNull(message = "finished not specified")
public Boolean isFinished() {
return finished;
}
public void setFinished(Boolean finished) {
this.finished = finished;
}
@AssertTrue(message = "there is a timesheet line in another work report marking as finished the same task")
public boolean checkConstraintOrderElementFinishedInAnotherWorkReport() {
if (!finished) {
return true;
}
List<WorkReportLine> lines = Registry.getWorkReportLineDAO()
.findByOrderElementNotInWorkReportAnotherTransaction(
orderElement, workReport);
for (WorkReportLine line : lines) {
if (line.isFinished()) {
return false;
}
}
return true;
}
} }

View file

@ -73,6 +73,88 @@
</update> </update>
</changeSet> </changeSet>
<changeSet id="add-columns-first-and-last-timesheet_date-to-sum_charged_effort"
author="mrego">
<comment>
Add columns first_timesheet_date and last_timesheet_date to
sum_charged_effort table
</comment>
<addColumn tableName="sum_charged_effort">
<column name="first_timesheet_date" type="DATETIME" />
</addColumn>
<addColumn tableName="sum_charged_effort">
<column name="last_timesheet_date" type="DATETIME" />
</addColumn>
</changeSet>
<changeSet id="add-new-column-read_only-to-advance_type" author="mrego">
<comment>
Add new column read_only with default value FALSE to advance_type
table.
</comment>
<addColumn tableName="advance_type">
<column name="read_only" type="BOOLEAN" />
</addColumn>
<addDefaultValue tableName="advance_type" columnName="read_only"
defaultValueBoolean="FALSE" />
<addNotNullConstraint tableName="advance_type"
columnName="read_only"
defaultNullValue="FALSE"
columnDataType="BOOLEAN" />
</changeSet>
<changeSet id="add-new-column-finished-to-work_report_line" author="mrego">
<comment>
Add new column finished with default value FALSE to
work_report_line table.
</comment>
<addColumn tableName="work_report_line">
<column name="finished" type="BOOLEAN" />
</addColumn>
<addDefaultValue tableName="work_report_line" columnName="finished"
defaultValueBoolean="FALSE" />
<addNotNullConstraint tableName="work_report_line"
columnName="finished"
defaultNullValue="FALSE"
columnDataType="BOOLEAN" />
</changeSet>
<changeSet id="add-new-column-finished_timesheets-to-sum_charged_effort"
author="mrego">
<comment>
Add new column finished_timesheets with default value FALSE to
sum_charged_effort table.
</comment>
<addColumn tableName="sum_charged_effort">
<column name="finished_timesheets" type="BOOLEAN" />
</addColumn>
<addDefaultValue tableName="sum_charged_effort"
columnName="finished_timesheets"
defaultValueBoolean="FALSE" />
<addNotNullConstraint tableName="sum_charged_effort"
columnName="finished_timesheets"
defaultNullValue="FALSE"
columnDataType="BOOLEAN" />
</changeSet>
<changeSet id="add-new-column-updated_from_timesheets-to-task_element"
author="mrego">
<comment>
Add new column updated_from_timesheets with default value FALSE to
task_element table.
</comment>
<addColumn tableName="task_element">
<column name="updated_from_timesheets" type="BOOLEAN" />
</addColumn>
<addDefaultValue tableName="task_element"
columnName="updated_from_timesheets"
defaultValueBoolean="FALSE" />
<addNotNullConstraint tableName="task_element"
columnName="updated_from_timesheets"
defaultNullValue="FALSE"
columnDataType="BOOLEAN" />
</changeSet>
<changeSet id="update-status-values-in-order_table" author="mrego"> <changeSet id="update-status-values-in-order_table" author="mrego">
<comment>Updating status values in order_table</comment> <comment>Updating status values in order_table</comment>
<update tableName="order_table"> <update tableName="order_table">

View file

@ -23,6 +23,7 @@
<property name="percentage" access="field"/> <property name="percentage" access="field"/>
<property name="qualityForm" access="field" <property name="qualityForm" access="field"
column="quality_form" /> column="quality_form" />
<property name="readOnly" column="read_only" />
</class> </class>

View file

@ -272,6 +272,14 @@
<property name="indirectChargedEffort" access="field" <property name="indirectChargedEffort" access="field"
column="indirect_charged_effort" column="indirect_charged_effort"
type="org.libreplan.business.workingday.hibernate.EffortDurationType" /> type="org.libreplan.business.workingday.hibernate.EffortDurationType" />
<property name="firstTimesheetDate" access="field"
column="first_timesheet_date" />
<property name="lastTimesheetDate" access="field"
column="last_timesheet_date" />
<property name="finishedTimesheets"
column="finished_timesheets" />
</class> </class>
<class name="SumExpenses" table="sum_expenses"> <class name="SumExpenses" table="sum_expenses">

View file

@ -52,6 +52,9 @@
<many-to-one name="calendar" column="base_calendar_id" cascade="none" <many-to-one name="calendar" column="base_calendar_id" cascade="none"
class="org.libreplan.business.calendars.entities.BaseCalendar"/> class="org.libreplan.business.calendars.entities.BaseCalendar"/>
<property name="updatedFromTimesheets"
column="updated_from_timesheets" />
<joined-subclass name="Task" table="task"> <joined-subclass name="Task" table="task">
<key column="task_element_id"></key> <key column="task_element_id"></key>
<property name="calculatedValue" column="calculated_value"> <property name="calculatedValue" column="calculated_value">

View file

@ -162,6 +162,8 @@
</composite-element> </composite-element>
</set> </set>
<property name="finished" />
</class> </class>
<class name="WorkReportLabelTypeAssigment" table="work_report_label_type_assigment"> <class name="WorkReportLabelTypeAssigment" table="work_report_label_type_assigment">

View file

@ -56,6 +56,7 @@ import org.zkoss.zkplus.databind.AnnotateDataBinder;
import org.zkoss.zkplus.databind.DataBinder; import org.zkoss.zkplus.databind.DataBinder;
import org.zkoss.zul.Bandbox; import org.zkoss.zul.Bandbox;
import org.zkoss.zul.Button; import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Combobox; import org.zkoss.zul.Combobox;
import org.zkoss.zul.Comboitem; import org.zkoss.zul.Comboitem;
import org.zkoss.zul.Datebox; import org.zkoss.zul.Datebox;
@ -67,7 +68,6 @@ import org.zkoss.zul.Radio;
import org.zkoss.zul.Row; import org.zkoss.zul.Row;
import org.zkoss.zul.Textbox; import org.zkoss.zul.Textbox;
import org.zkoss.zul.Timebox; import org.zkoss.zul.Timebox;
import org.zkoss.zul.api.Checkbox;
import org.zkoss.zul.api.Column; import org.zkoss.zul.api.Column;
/** /**
@ -446,7 +446,7 @@ public class Util {
* The {@link Setter} interface that will implement a set method. * The {@link Setter} interface that will implement a set method.
* @return The {@link Checkbox} bound * @return The {@link Checkbox} bound
*/ */
public static <C extends Checkbox> C bind(final C checkBox, public static Checkbox bind(final Checkbox checkBox,
final Getter<Boolean> getter, final Setter<Boolean> setter) { final Getter<Boolean> getter, final Setter<Boolean> setter) {
checkBox.setChecked(getter.get()); checkBox.setChecked(getter.get());
checkBox.addEventListener(Events.ON_CHECK, new EventListener() { checkBox.addEventListener(Events.ON_CHECK, new EventListener() {

View file

@ -24,7 +24,7 @@ import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.web.common.Util; import org.libreplan.web.common.Util;
import org.libreplan.web.common.Util.Getter; import org.libreplan.web.common.Util.Getter;
import org.libreplan.web.common.Util.Setter; import org.libreplan.web.common.Util.Setter;
import org.zkoss.zul.api.Checkbox; import org.zkoss.zul.Checkbox;
/** /**
* It configures some ZK components to work together and edit a Capacity object * It configures some ZK components to work together and edit a Capacity object

View file

@ -99,6 +99,8 @@ public interface IManageOrderElementAdvancesModel {
public boolean isQualityForm(AdvanceAssignment advance); public boolean isQualityForm(AdvanceAssignment advance);
public boolean isReadOnly(AdvanceAssignment advance);
public boolean lessThanPreviousMeasurements(); public boolean lessThanPreviousMeasurements();
public boolean hasConsolidatedAdvances(AdvanceAssignment advance); public boolean hasConsolidatedAdvances(AdvanceAssignment advance);
@ -134,4 +136,5 @@ public interface IManageOrderElementAdvancesModel {
Boolean isAlreadyReportedProgressWith(LocalDate date); Boolean isAlreadyReportedProgressWith(LocalDate date);
} }

View file

@ -366,7 +366,10 @@ public class ManageOrderElementAdvancesController extends
if (advance.getAdvanceType() != null) { if (advance.getAdvanceType() != null) {
isQualityForm = manageOrderElementAdvancesModel isQualityForm = manageOrderElementAdvancesModel
.isQualityForm(advance); .isQualityForm(advance);
if (manageOrderElementAdvancesModel readOnlyAdvance = manageOrderElementAdvancesModel
.isReadOnly(advance);
if (!readOnlyAdvance
&& manageOrderElementAdvancesModel
.isSubcontratedAdvanceTypeAndSubcontratedTask(advance)) { .isSubcontratedAdvanceTypeAndSubcontratedTask(advance)) {
readOnlyAdvance = true; readOnlyAdvance = true;
} }
@ -375,12 +378,12 @@ public class ManageOrderElementAdvancesController extends
if ((advance instanceof DirectAdvanceAssignment) if ((advance instanceof DirectAdvanceAssignment)
&& ((DirectAdvanceAssignment) advance) && ((DirectAdvanceAssignment) advance)
.getAdvanceMeasurements().isEmpty() .getAdvanceMeasurements().isEmpty()
&& !isQualityForm) { && !isQualityForm && !readOnlyAdvance) {
appendComboboxAdvanceType(listItem); appendComboboxAdvanceType(listItem);
} else { } else {
appendLabelAdvanceType(listItem); appendLabelAdvanceType(listItem);
} }
appendDecimalBoxMaxValue(listItem, isQualityForm); appendDecimalBoxMaxValue(listItem, isQualityForm || readOnlyAdvance);
appendDecimalBoxValue(listItem); appendDecimalBoxValue(listItem);
appendLabelPercentage(listItem); appendLabelPercentage(listItem);
appendDateBoxDate(listItem); appendDateBoxDate(listItem);
@ -401,7 +404,8 @@ public class ManageOrderElementAdvancesController extends
for(AdvanceType advanceType : listAdvanceType){ for(AdvanceType advanceType : listAdvanceType){
if (!advanceType.getUnitName().equals( if (!advanceType.getUnitName().equals(
PredefinedAdvancedTypes.CHILDREN.getTypeName()) PredefinedAdvancedTypes.CHILDREN.getTypeName())
&& !advanceType.isQualityForm()) { && !advanceType.isQualityForm()
&& !advanceType.isReadOnly()) {
Comboitem comboItem = new Comboitem(); Comboitem comboItem = new Comboitem();
comboItem.setValue(advanceType); comboItem.setValue(advanceType);
comboItem.setLabel(advanceType.getUnitName()); comboItem.setLabel(advanceType.getUnitName());
@ -461,7 +465,7 @@ public class ManageOrderElementAdvancesController extends
} }
private void appendDecimalBoxMaxValue(final Listitem listItem, private void appendDecimalBoxMaxValue(final Listitem listItem,
boolean isQualityForm) { boolean isQualityFormOrReadOnly) {
final AdvanceAssignment advanceAssignment = (AdvanceAssignment) listItem final AdvanceAssignment advanceAssignment = (AdvanceAssignment) listItem
.getValue(); .getValue();
final Decimalbox maxValue = new Decimalbox(); final Decimalbox maxValue = new Decimalbox();
@ -469,7 +473,7 @@ public class ManageOrderElementAdvancesController extends
final DirectAdvanceAssignment directAdvanceAssignment; final DirectAdvanceAssignment directAdvanceAssignment;
if ((advanceAssignment instanceof IndirectAdvanceAssignment) if ((advanceAssignment instanceof IndirectAdvanceAssignment)
|| isQualityForm || isQualityFormOrReadOnly
|| (advanceAssignment.getAdvanceType() != null && advanceAssignment || (advanceAssignment.getAdvanceType() != null && advanceAssignment
.getAdvanceType().getPercentage()) .getAdvanceType().getPercentage())
|| manageOrderElementAdvancesModel || manageOrderElementAdvancesModel
@ -708,6 +712,11 @@ public class ManageOrderElementAdvancesController extends
addMeasurementButton.setDisabled(true); addMeasurementButton.setDisabled(true);
addMeasurementButton addMeasurementButton
.setTooltiptext(_("Progress that are reported by quality forms can not be modified")); .setTooltiptext(_("Progress that are reported by quality forms can not be modified"));
} else if ((advance.getAdvanceType() != null)
&& (advance.getAdvanceType().isReadOnly())) {
addMeasurementButton.setDisabled(true);
addMeasurementButton
.setTooltiptext(_("This progress type cannot be modified"));
} else if (advance instanceof IndirectAdvanceAssignment) { } else if (advance instanceof IndirectAdvanceAssignment) {
addMeasurementButton.setDisabled(true); addMeasurementButton.setDisabled(true);
addMeasurementButton addMeasurementButton
@ -739,6 +748,11 @@ public class ManageOrderElementAdvancesController extends
removeButton.setDisabled(true); removeButton.setDisabled(true);
removeButton removeButton
.setTooltiptext(_("Progress that are reported by quality forms cannot be modified")); .setTooltiptext(_("Progress that are reported by quality forms cannot be modified"));
} else if ((advance.getAdvanceType() != null)
&& (advance.getAdvanceType().isReadOnly())) {
removeButton.setDisabled(true);
removeButton
.setTooltiptext(_("This progress type cannot be modified"));
} else if (advance instanceof IndirectAdvanceAssignment) { } else if (advance instanceof IndirectAdvanceAssignment) {
removeButton.setDisabled(true); removeButton.setDisabled(true);
removeButton removeButton
@ -1219,6 +1233,11 @@ public class ManageOrderElementAdvancesController extends
removeButton.setDisabled(true); removeButton.setDisabled(true);
removeButton removeButton
.setTooltiptext(_("Progress measurements that are reported by quality forms cannot be removed")); .setTooltiptext(_("Progress measurements that are reported by quality forms cannot be removed"));
} else if ((advance.getAdvanceType() != null)
&& (advance.getAdvanceType().isReadOnly())) {
removeButton.setDisabled(true);
removeButton
.setTooltiptext(_("This progress type cannot cannot be removed"));
} else if (advance.isFake()) { } else if (advance.isFake()) {
removeButton.setDisabled(true); removeButton.setDisabled(true);
removeButton removeButton

View file

@ -161,7 +161,8 @@ public class ManageOrderElementAdvancesModel implements
for (AdvanceAssignment advance : listAdvanceAssignmentsCopy) { for (AdvanceAssignment advance : listAdvanceAssignmentsCopy) {
if ((!listAdvanceAssignments.contains(advance)) if ((!listAdvanceAssignments.contains(advance))
&& (advance instanceof DirectAdvanceAssignment) && (advance instanceof DirectAdvanceAssignment)
&& (!advance.getAdvanceType().isQualityForm())) { && (!advance.getAdvanceType().isQualityForm())
&& (!advance.getAdvanceType().isReadOnly())) {
listAdvanceAssignments.add(advance); listAdvanceAssignments.add(advance);
} }
} }
@ -337,7 +338,8 @@ public class ManageOrderElementAdvancesModel implements
for (AdvanceType advanceType : this.listAdvanceTypes) { for (AdvanceType advanceType : this.listAdvanceTypes) {
if ((advanceType.getUnitName() if ((advanceType.getUnitName()
.equals(PredefinedAdvancedTypes.CHILDREN.getTypeName())) .equals(PredefinedAdvancedTypes.CHILDREN.getTypeName()))
|| (advanceType.isQualityForm())) { || (advanceType.isQualityForm())
|| advanceType.isReadOnly()) {
continue; continue;
} }
if (existsAdvanceTypeAlreadyInThisOrderElement(advanceType)) { if (existsAdvanceTypeAlreadyInThisOrderElement(advanceType)) {
@ -412,6 +414,9 @@ public class ManageOrderElementAdvancesModel implements
if (advanceType.isQualityForm()) { if (advanceType.isQualityForm()) {
return true; return true;
} }
if (advanceType.isReadOnly()) {
return true;
}
} }
if(isIndirectAdvanceAssignment){ if(isIndirectAdvanceAssignment){
@ -764,6 +769,14 @@ public class ManageOrderElementAdvancesModel implements
return advanceType.isQualityForm(); return advanceType.isQualityForm();
} }
@Override
@Transactional(readOnly = true)
public boolean isReadOnly(AdvanceAssignment advance) {
AdvanceType advanceType = advance.getAdvanceType();
advanceTypeDAO.reattach(advanceType);
return advanceType.isReadOnly();
}
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public boolean findIndirectConsolidation( public boolean findIndirectConsolidation(

View file

@ -1203,7 +1203,8 @@ _(
@Override @Override
public boolean isFixed() { public boolean isFixed() {
return taskElement.isLimitingAndHasDayAssignments() return taskElement.isLimitingAndHasDayAssignments()
|| taskElement.hasConsolidations(); || taskElement.hasConsolidations()
|| taskElement.isUpdatedFromTimesheets();
} }
@Override @Override
@ -1222,6 +1223,29 @@ _(
return taskElement.isRoot(); return taskElement.isRoot();
} }
@Override
public boolean isUpdatedFromTimesheets() {
return taskElement.isUpdatedFromTimesheets();
}
@Override
public Date getFirstTimesheetDate() {
OrderElement orderElement = taskElement.getOrderElement();
if (orderElement != null) {
return orderElement.getFirstTimesheetDate();
}
return null;
}
@Override
public Date getLastTimesheetDate() {
OrderElement orderElement = taskElement.getOrderElement();
if (orderElement != null) {
return orderElement.getLastTimesheetDate();
}
return null;
}
} }
@Override @Override

View file

@ -0,0 +1,242 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 Igalia, S.L.
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.planner.adaptplanning;
import static org.libreplan.web.I18nHelper._;
import java.util.Date;
import java.util.List;
import org.joda.time.LocalDate;
import org.libreplan.business.advance.bootstrap.PredefinedAdvancedTypes;
import org.libreplan.business.advance.entities.AdvanceMeasurement;
import org.libreplan.business.advance.entities.AdvanceType;
import org.libreplan.business.advance.entities.DirectAdvanceAssignment;
import org.libreplan.business.advance.exceptions.DuplicateAdvanceAssignmentForOrderElementException;
import org.libreplan.business.advance.exceptions.DuplicateValueTrueReportGlobalAdvanceException;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.planner.entities.PositionConstraintType;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.web.planner.order.PlanningStateCreator.PlanningState;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.zkoss.ganttz.Planner;
import org.zkoss.ganttz.TaskComponent;
import org.zkoss.ganttz.extensions.IContext;
import org.zkoss.ganttz.util.LongOperationFeedback;
import org.zkoss.ganttz.util.LongOperationFeedback.ILongOperation;
/**
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class AdaptPlanningCommand implements IAdaptPlanningCommand {
private PlanningState planningState;
@Override
public String getName() {
return _("Adapt planning acording to timesheets");
}
@Override
public void doAction(final IContext<TaskElement> context) {
LongOperationFeedback.execute(context.getRelativeTo(),
new ILongOperation() {
@Override
public String getName() {
return _("Adapting planning according to timesheets");
}
@Override
public void doAction() throws Exception {
adaptPlanning(context);
}
});
}
private void adaptPlanning(IContext<TaskElement> context) {
List<TaskElement> taskElements = planningState.getRootTask()
.getAllChildren();
for (TaskElement taskElement : taskElements) {
// Only adapt task leafs
if (!taskElement.isLeaf()) {
continue;
}
OrderElement orderElement = taskElement.getOrderElement();
// Reset status to allow move the task if needed while adapting the
// planning
taskElement.setUpdatedFromTimesheets(false);
if (orderElement.hasTimesheetsReportingHours()) {
setStartDateAndConstraint(taskElement,
orderElement.getFirstTimesheetDate());
Date lastTimesheetDate = orderElement.getLastTimesheetDate();
setEndDateIfNeeded(taskElement, lastTimesheetDate);
if (orderElement.isFinishedTimesheets()) {
setEndDate(taskElement, lastTimesheetDate);
addTimesheetsProgress(orderElement, lastTimesheetDate);
removeResourceAllocationsBeyondEndDate(taskElement);
} else {
removeTimesheetsProgressIfAny(orderElement);
}
taskElement.setUpdatedFromTimesheets(true);
}
}
for (TaskElement taskElement : taskElements) {
if (taskElement.isUpdatedFromTimesheets()) {
updateTask(context, taskElement);
}
}
((Planner) context.getRelativeTo()).invalidate();
context.reloadCharts();
}
private void removeResourceAllocationsBeyondEndDate(TaskElement taskElement) {
LocalDate endDate = taskElement.getEndAsLocalDate();
for (ResourceAllocation<?> resourceAllocation : taskElement
.getAllResourceAllocations()) {
resourceAllocation.removeDayAssignmentsBeyondDate(endDate);
}
}
private void setStartDateAndConstraint(TaskElement taskElement,
Date startDate) {
taskElement.setStartDate(startDate);
setStartInFixedDateConstarint(taskElement, startDate);
}
private void setStartInFixedDateConstarint(TaskElement taskElement,
Date startDate) {
if (taskElement.isTask()) {
Task task = (Task) taskElement;
task.getPositionConstraint()
.update(PositionConstraintType.START_IN_FIXED_DATE,
IntraDayDate.startOfDay(LocalDate
.fromDateFields(startDate)));
}
}
private void setEndDateIfNeeded(TaskElement taskElement, Date endDate) {
if (taskElement.getEndDate().compareTo(endDate) <= 0) {
setEndDate(taskElement, endDate);
}
}
private void setEndDate(TaskElement taskElement, Date endDate) {
taskElement.setEndDate(LocalDate.fromDateFields(endDate).plusDays(1)
.toDateTimeAtStartOfDay().toDate());
}
private void addTimesheetsProgress(OrderElement orderElement,
Date progressDate) {
AdvanceType timesheetsAdvanceType = getTimesheetsAdvanceType();
DirectAdvanceAssignment timesheetsAdvanceAssignment = orderElement
.getDirectAdvanceAssignmentByType(timesheetsAdvanceType);
if (timesheetsAdvanceAssignment == null) {
timesheetsAdvanceAssignment = DirectAdvanceAssignment.create(false,
timesheetsAdvanceType.getDefaultMaxValue());
timesheetsAdvanceAssignment.setAdvanceType(timesheetsAdvanceType);
try {
orderElement.addAdvanceAssignment(timesheetsAdvanceAssignment);
} catch (DuplicateValueTrueReportGlobalAdvanceException e) {
// This shouldn't happen as the new advanceAssignment is not
// marked as spread yet
throw new RuntimeException(e);
} catch (DuplicateAdvanceAssignmentForOrderElementException e) {
// If the same type already exists in other element we don't do
// anything
return;
}
}
DirectAdvanceAssignment spreadAdvanceAssignment = orderElement
.getReportGlobalAdvanceAssignment();
if (spreadAdvanceAssignment != null) {
spreadAdvanceAssignment.setReportGlobalAdvance(false);
}
timesheetsAdvanceAssignment.setReportGlobalAdvance(true);
timesheetsAdvanceAssignment.resetAdvanceMeasurements(AdvanceMeasurement
.create(LocalDate.fromDateFields(progressDate),
timesheetsAdvanceType.getDefaultMaxValue()));
}
private AdvanceType getTimesheetsAdvanceType() {
return PredefinedAdvancedTypes.TIMESHEETS.getType();
}
private void removeTimesheetsProgressIfAny(OrderElement orderElement) {
DirectAdvanceAssignment timesheetsAdvanceAssignment = orderElement
.getDirectAdvanceAssignmentByType(getTimesheetsAdvanceType());
if (timesheetsAdvanceAssignment != null) {
orderElement.removeAdvanceAssignment(timesheetsAdvanceAssignment);
}
}
private void updateTask(IContext<TaskElement> context,
TaskElement taskElement) {
taskElement.updateAdvancePercentageFromOrderElement();
Planner planner = (Planner) context.getRelativeTo();
TaskComponent taskComponent = planner.getTaskComponentRelatedTo(context
.getMapper().findAssociatedBean(taskElement));
if (taskComponent != null) {
taskComponent.updateTooltipText();
taskComponent.updateProperties();
taskComponent.invalidate();
}
context.recalculatePosition(taskElement);
}
@Override
public String getImage() {
return "/common/img/ico_adapt_planning.png";
}
@Override
public boolean isDisabled() {
return false;
}
@Override
public void setState(PlanningState planningState) {
this.planningState = planningState;
}
@Override
public boolean isPlannerCommand() {
return true;
}
}

View file

@ -0,0 +1,35 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 Igalia, S.L.
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.planner.adaptplanning;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.web.planner.order.PlanningStateCreator.PlanningState;
import org.zkoss.ganttz.extensions.ICommand;
/**
* Command to adapt planning of a project taking into account information from
* the timesheets.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface IAdaptPlanningCommand extends ICommand<TaskElement> {
public void setState(PlanningState planningState);
}

View file

@ -1129,14 +1129,14 @@ class Row {
} }
private EffortDurationBox buildSumAllEffort() { private EffortDurationBox buildSumAllEffort() {
EffortDurationBox box = (isGroupingRow() || isLimiting) ? EffortDurationBox EffortDurationBox box = isEffortDurationBoxDisabled() ? EffortDurationBox
.notEditable() : new EffortDurationBox(); .notEditable() : new EffortDurationBox();
box.setWidth("40px"); box.setWidth("40px");
return box; return box;
} }
private void addListenerIfNeeded(Component allEffortComponent) { private void addListenerIfNeeded(Component allEffortComponent) {
if (isGroupingRow() || isLimiting) { if (isEffortDurationBoxDisabled()) {
return; return;
} }
final EffortDurationBox effortDurationBox = (EffortDurationBox) allEffortComponent; final EffortDurationBox effortDurationBox = (EffortDurationBox) allEffortComponent;
@ -1167,6 +1167,10 @@ class Row {
}); });
} }
private boolean isEffortDurationBoxDisabled() {
return isGroupingRow() || isLimiting || task.isUpdatedFromTimesheets();
}
private void reloadEffortsSameRowForDetailItems() { private void reloadEffortsSameRowForDetailItems() {
for (Entry<DetailItem, Component> entry : componentsByDetailItem for (Entry<DetailItem, Component> entry : componentsByDetailItem
.entrySet()) { .entrySet()) {
@ -1181,7 +1185,7 @@ class Row {
EffortDuration allEffort = aggregate.getTotalEffort(); EffortDuration allEffort = aggregate.getTotalEffort();
allEffortInput.setValue(allEffort); allEffortInput.setValue(allEffort);
Clients.closeErrorBox(allEffortInput); Clients.closeErrorBox(allEffortInput);
if (isLimiting) { if (isEffortDurationBoxDisabled()) {
allEffortInput.setDisabled(true); allEffortInput.setDisabled(true);
} }
if (restriction.isInvalidTotalEffort(allEffort)) { if (restriction.isInvalidTotalEffort(allEffort)) {
@ -1215,6 +1219,11 @@ class Row {
hboxAssigmentFunctionsCombo.appendChild(assignmentFunctionsCombo); hboxAssigmentFunctionsCombo.appendChild(assignmentFunctionsCombo);
assignmentFunctionsConfigureButton = getAssignmentFunctionsConfigureButton(assignmentFunctionsCombo); assignmentFunctionsConfigureButton = getAssignmentFunctionsConfigureButton(assignmentFunctionsCombo);
hboxAssigmentFunctionsCombo.appendChild(assignmentFunctionsConfigureButton); hboxAssigmentFunctionsCombo.appendChild(assignmentFunctionsConfigureButton);
// Disable if task is updated from timesheets
assignmentFunctionsCombo.setDisabled(task.isUpdatedFromTimesheets());
assignmentFunctionsConfigureButton.setDisabled(task
.isUpdatedFromTimesheets());
} }
/** /**
@ -1621,7 +1630,8 @@ class Row {
private boolean cannotBeEdited(DetailItem item) { private boolean cannotBeEdited(DetailItem item) {
return isGroupingRow() || doesNotIntersectWithTask(item) return isGroupingRow() || doesNotIntersectWithTask(item)
|| isBeforeLatestConsolidation(item); || isBeforeLatestConsolidation(item)
|| task.isUpdatedFromTimesheets();
} }
private EffortDurationBox disableIfNeeded(DetailItem item, private EffortDurationBox disableIfNeeded(DetailItem item,

View file

@ -107,7 +107,8 @@ public class AllocationConfiguration extends HtmlMacroComponent {
.getCalculatedValue())) { .getCalculatedValue())) {
radio.setChecked(true); radio.setChecked(true);
} }
radio.setDisabled(formBinder.isAnyManual()); radio.setDisabled(formBinder.isAnyManual()
|| formBinder.isTaskUpdatedFromTimesheets());
} }
} }
}; };

View file

@ -423,4 +423,9 @@ public class AllocationRowsHandler {
private ArrayList<AllocationRow> copyOfCurrentRowsToAvoidConcurrentModification() { private ArrayList<AllocationRow> copyOfCurrentRowsToAvoidConcurrentModification() {
return new ArrayList<AllocationRow>(currentRows); return new ArrayList<AllocationRow>(currentRows);
} }
public boolean isTaskUpdatedFromTimesheets() {
return task.isUpdatedFromTimesheets();
}
} }

View file

@ -217,7 +217,7 @@ public class FormBinder {
boolean disabled = rows.isEmpty() boolean disabled = rows.isEmpty()
|| (CalculatedValue.NUMBER_OF_HOURS == c) || (CalculatedValue.NUMBER_OF_HOURS == c)
|| (c == CalculatedValue.RESOURCES_PER_DAY && !recommendedAllocation) || (c == CalculatedValue.RESOURCES_PER_DAY && !recommendedAllocation)
|| isAnyManual(); || isAnyManual() || isTaskUpdatedFromTimesheets();
this.effortInput.setDisabled(disabled); this.effortInput.setDisabled(disabled);
} }
@ -245,13 +245,14 @@ public class FormBinder {
allResourcesPerDayVisibilityRule(); allResourcesPerDayVisibilityRule();
applyDisabledRulesOnRows(); applyDisabledRulesOnRows();
this.btnRecommendedAllocation.setDisabled(recommendedAllocation this.btnRecommendedAllocation.setDisabled(recommendedAllocation
|| isAnyManual()); || isAnyManual() || isTaskUpdatedFromTimesheets());
} }
private void applyDisabledRulesOnRows() { private void applyDisabledRulesOnRows() {
for (AllocationRow each : rows) { for (AllocationRow each : rows) {
each.applyDisabledRules(getCalculatedValue(), each.applyDisabledRules(getCalculatedValue(),
recommendedAllocation, isAnyManual()); recommendedAllocation, isAnyManual()
|| isTaskUpdatedFromTimesheets());
} }
} }
@ -361,7 +362,7 @@ public class FormBinder {
void applyDisabledRules() { void applyDisabledRules() {
this.taskWorkableDays.setDisabled(allocationRowsHandler this.taskWorkableDays.setDisabled(allocationRowsHandler
.getCalculatedValue() == CalculatedValue.END_DATE .getCalculatedValue() == CalculatedValue.END_DATE
|| isAnyManual()); || isAnyManual() || isTaskUpdatedFromTimesheets());
} }
private void initializeDateAndDurationFieldsFromTaskOriginalValues() { private void initializeDateAndDurationFieldsFromTaskOriginalValues() {
@ -465,7 +466,8 @@ public class FormBinder {
CalculatedValue c = allocationRowsHandler.getCalculatedValue(); CalculatedValue c = allocationRowsHandler.getCalculatedValue();
this.allResourcesPerDay.setDisabled(rows.isEmpty() this.allResourcesPerDay.setDisabled(rows.isEmpty()
|| c == CalculatedValue.RESOURCES_PER_DAY || c == CalculatedValue.RESOURCES_PER_DAY
|| !recommendedAllocation || isAnyManual()); || !recommendedAllocation || isAnyManual()
|| isTaskUpdatedFromTimesheets());
this.allResourcesPerDay this.allResourcesPerDay
.setConstraint(constraintForAllResourcesPerDay()); .setConstraint(constraintForAllResourcesPerDay());
} }
@ -522,6 +524,10 @@ public class FormBinder {
* exit the edition form * exit the edition form
*/ */
public boolean accept() { public boolean accept() {
if (isTaskUpdatedFromTimesheets()) {
return true;
}
Flagged<AllocationResult, Warnings> result = resourceAllocationModel Flagged<AllocationResult, Warnings> result = resourceAllocationModel
.accept(); .accept();
@ -707,7 +713,8 @@ public class FormBinder {
public void setRecommendedAllocation(Button recommendedAllocation) { public void setRecommendedAllocation(Button recommendedAllocation) {
this.btnRecommendedAllocation = recommendedAllocation; this.btnRecommendedAllocation = recommendedAllocation;
this.btnRecommendedAllocation.setDisabled(isAnyManual()); this.btnRecommendedAllocation.setDisabled(isAnyManual()
|| isTaskUpdatedFromTimesheets());
Util.ensureUniqueListener(recommendedAllocation, Events.ON_CLICK, Util.ensureUniqueListener(recommendedAllocation, Events.ON_CLICK,
new EventListener() { new EventListener() {
@Override @Override
@ -939,4 +946,8 @@ public class FormBinder {
return false; return false;
} }
public boolean isTaskUpdatedFromTimesheets() {
return allocationRowsHandler.isTaskUpdatedFromTimesheets();
}
} }

View file

@ -602,7 +602,7 @@ public class ResourceAllocationController extends GenericForwardComposer {
// On click delete button // On click delete button
Button deleteButton = appendDeleteButton(row); Button deleteButton = appendDeleteButton(row);
deleteButton.setDisabled(isAnyManual()); deleteButton.setDisabled(isAnyManualOrTaskUpdatedFromTimesheets());
formBinder.setDeleteButtonFor(data, deleteButton); formBinder.setDeleteButtonFor(data, deleteButton);
deleteButton.addEventListener("onClick", new EventListener() { deleteButton.addEventListener("onClick", new EventListener() {
@ -671,8 +671,12 @@ public class ResourceAllocationController extends GenericForwardComposer {
return formBinder != null && formBinder.isAnyNotFlat(); return formBinder != null && formBinder.isAnyNotFlat();
} }
public boolean isAnyManual() { public boolean isAnyManualOrTaskUpdatedFromTimesheets() {
return formBinder != null && formBinder.isAnyManual(); if (formBinder == null) {
return false;
}
return formBinder.isAnyManual()
|| formBinder.isTaskUpdatedFromTimesheets();
} }
} }

View file

@ -71,6 +71,7 @@ import org.libreplan.business.users.entities.UserRole;
import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.web.calendars.BaseCalendarModel; import org.libreplan.web.calendars.BaseCalendarModel;
import org.libreplan.web.common.ViewSwitcher; import org.libreplan.web.common.ViewSwitcher;
import org.libreplan.web.planner.adaptplanning.IAdaptPlanningCommand;
import org.libreplan.web.planner.advances.AdvanceAssignmentPlanningController; import org.libreplan.web.planner.advances.AdvanceAssignmentPlanningController;
import org.libreplan.web.planner.advances.IAdvanceAssignmentPlanningCommand; import org.libreplan.web.planner.advances.IAdvanceAssignmentPlanningCommand;
import org.libreplan.web.planner.allocation.IAdvancedAllocationCommand; import org.libreplan.web.planner.allocation.IAdvancedAllocationCommand;
@ -222,6 +223,9 @@ public class OrderPlanningModel implements IOrderPlanningModel {
@Autowired @Autowired
private IReassignCommand reassignCommand; private IReassignCommand reassignCommand;
@Autowired
private IAdaptPlanningCommand adaptPlanningCommand;
@Autowired @Autowired
private IResourceAllocationCommand resourceAllocationCommand; private IResourceAllocationCommand resourceAllocationCommand;
@ -332,6 +336,7 @@ public class OrderPlanningModel implements IOrderPlanningModel {
configuration.addGlobalCommand(buildReassigningCommand()); configuration.addGlobalCommand(buildReassigningCommand());
configuration.addGlobalCommand(buildCancelEditionCommand()); configuration.addGlobalCommand(buildCancelEditionCommand());
configuration.addGlobalCommand(buildAdaptPlanningCommand());
NullSeparatorCommandOnTask<TaskElement> separator = new NullSeparatorCommandOnTask<TaskElement>(); NullSeparatorCommandOnTask<TaskElement> separator = new NullSeparatorCommandOnTask<TaskElement>();
@ -1055,6 +1060,11 @@ public class OrderPlanningModel implements IOrderPlanningModel {
return reassignCommand; return reassignCommand;
} }
private ICommand<TaskElement> buildAdaptPlanningCommand() {
adaptPlanningCommand.setState(planningState);
return adaptPlanningCommand;
}
private ICommand<TaskElement> buildCancelEditionCommand() { private ICommand<TaskElement> buildCancelEditionCommand() {
return new ICommand<TaskElement>() { return new ICommand<TaskElement>() {
@ -1097,6 +1107,11 @@ public class OrderPlanningModel implements IOrderPlanningModel {
return false; return false;
} }
@Override
public boolean isPlannerCommand() {
return false;
}
}; };
} }

View file

@ -1038,6 +1038,11 @@ public class SaveCommandBuilder {
return disabled; return disabled;
} }
@Override
public boolean isPlannerCommand() {
return false;
}
} }
private static final class LabelCreatorForInvalidValues implements private static final class LabelCreatorForInvalidValues implements

View file

@ -375,4 +375,9 @@ public class ReassignCommand implements IReassignCommand {
return false; return false;
} }
@Override
public boolean isPlannerCommand() {
return true;
}
} }

View file

@ -61,6 +61,9 @@ public class ReassignConfiguration {
} }
private boolean isChoosenForReassignation(Task each) { private boolean isChoosenForReassignation(Task each) {
if (each.isUpdatedFromTimesheets()) {
return false;
}
return type == Type.ALL || isAfterDate(each); return type == Type.ALL || isAfterDate(each);
} }

View file

@ -138,7 +138,8 @@ public class TaskPropertiesController extends GenericForwardComposer {
disabledConstraintsAndAllocations = currentTaskElement disabledConstraintsAndAllocations = currentTaskElement
.isSubcontractedAndWasAlreadySent() .isSubcontractedAndWasAlreadySent()
|| currentTaskElement.isLimitingAndHasDayAssignments(); || currentTaskElement.isLimitingAndHasDayAssignments()
|| currentTaskElement.isUpdatedFromTimesheets();
if (!disabledConstraintsAndAllocations && (currentTaskElement.isTask())) { if (!disabledConstraintsAndAllocations && (currentTaskElement.isTask())) {
disabledConstraintsAndAllocations = ((Task) currentTaskElement) disabledConstraintsAndAllocations = ((Task) currentTaskElement)
.isManualAnyAllocation(); .isManualAnyAllocation();

View file

@ -116,6 +116,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree); viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
getModel().indent(element); getModel().indent(element);
filterByPredicateIfAny(); filterByPredicateIfAny();
updateControlButtons();
} }
public TreeModel getTreeModel() { public TreeModel getTreeModel() {
@ -140,6 +141,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree); viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
getModel().unindent(element); getModel().unindent(element);
filterByPredicateIfAny(); filterByPredicateIfAny();
updateControlButtons();
} }
public void up() { public void up() {
@ -153,6 +155,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree); viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
getModel().up(element); getModel().up(element);
filterByPredicateIfAny(); filterByPredicateIfAny();
updateControlButtons();
} }
public void down() { public void down() {
@ -165,6 +168,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree); viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
getModel().down(element); getModel().down(element);
filterByPredicateIfAny(); filterByPredicateIfAny();
updateControlButtons();
} }
public T getSelectedNode() { public T getSelectedNode() {
@ -184,8 +188,15 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree); viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
Treerow from = (Treerow) dragged; T fromNode = null;
T fromNode = type.cast(((Treeitem) from.getParent()).getValue()); if (dragged instanceof Treerow) {
Treerow from = (Treerow) dragged;
fromNode = type.cast(((Treeitem) from.getParent()).getValue());
}
if (dragged instanceof Treeitem) {
Treeitem from = (Treeitem) dragged;
fromNode = type.cast(from.getValue());
}
if (dropedIn instanceof Tree) { if (dropedIn instanceof Tree) {
getModel().moveToRoot(fromNode); getModel().moveToRoot(fromNode);
} }
@ -354,6 +365,15 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
private List<Column> columns; private List<Column> columns;
private Button btnNewFromTemplate;
private Button downButton;
private Button upButton;
private Button leftButton;
private Button rightButton;
protected TreeViewStateSnapshot getSnapshotOfOpenedNodes() { protected TreeViewStateSnapshot getSnapshotOfOpenedNodes() {
return viewStateSnapshot; return viewStateSnapshot;
@ -361,6 +381,13 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
private void resetControlButtons() { private void resetControlButtons() {
btnNew.setDisabled(isNewButtonDisabled()); btnNew.setDisabled(isNewButtonDisabled());
btnNewFromTemplate.setDisabled(isNewButtonDisabled());
boolean disabled = readOnly || isPredicateApplied();
downButton.setDisabled(disabled);
upButton.setDisabled(disabled);
leftButton.setDisabled(disabled);
rightButton.setDisabled(disabled);
} }
protected abstract boolean isNewButtonDisabled(); protected abstract boolean isNewButtonDisabled();
@ -692,8 +719,8 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
public void render(final Treeitem item, Object data) { public void render(final Treeitem item, Object data) {
item.setValue(data); item.setValue(data);
applySnapshot(item); applySnapshot(item);
currentTreeRow = getTreeRowWithoutChildrenFor(item);
final T currentElement = type.cast(data); final T currentElement = type.cast(data);
currentTreeRow = getTreeRowWithoutChildrenFor(item, currentElement);
createCells(item, currentElement); createCells(item, currentElement);
onDropMoveFromDraggedToTarget(); onDropMoveFromDraggedToTarget();
} }
@ -722,10 +749,17 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
} }
} }
private Treerow getTreeRowWithoutChildrenFor(final Treeitem item) { private Treerow getTreeRowWithoutChildrenFor(final Treeitem item,
T element) {
Treerow result = createOrRetrieveFor(item); Treerow result = createOrRetrieveFor(item);
// Attach treecells to treerow // Attach treecells to treerow
result.setDroppable("true"); if (element.isUpdatedFromTimesheets()) {
result.setDraggable("false");
result.setDroppable("false");
} else {
result.setDraggable("true");
result.setDroppable("true");
}
result.getChildren().clear(); result.getChildren().clear();
return result; return result;
} }
@ -766,7 +800,8 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
final SchedulingState schedulingState = getSchedulingStateFrom(currentElement); final SchedulingState schedulingState = getSchedulingStateFrom(currentElement);
SchedulingStateToggler schedulingStateToggler = new SchedulingStateToggler( SchedulingStateToggler schedulingStateToggler = new SchedulingStateToggler(
schedulingState); schedulingState);
schedulingStateToggler.setReadOnly(readOnly); schedulingStateToggler.setReadOnly(readOnly
|| currentElement.isUpdatedFromTimesheets());
final Treecell cell = addCell( final Treecell cell = addCell(
getDecorationFromState(getSchedulingStateFrom(currentElement)), getDecorationFromState(getSchedulingStateFrom(currentElement)),
schedulingStateToggler); schedulingStateToggler);
@ -796,7 +831,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
} }
}); });
schedulingStateToggler.afterCompose(); schedulingStateToggler.afterCompose();
cell.setDraggable("true");
} }
protected abstract SchedulingState getSchedulingStateFrom( protected abstract SchedulingState getSchedulingStateFrom(
@ -1105,92 +1140,6 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
protected abstract void onDoubleClickForSchedulingStateCell( protected abstract void onDoubleClickForSchedulingStateCell(
T currentElement); T currentElement);
protected Button createDownButton(final Treeitem item,
final T currentElement) {
EventListener downButtonListener = new EventListener() {
@Override
public void onEvent(Event event) {
down(currentElement);
}
};
Button result;
if (isPredicateApplied() || isLastItem(currentElement) || readOnly) {
result = createButton("/common/img/ico_bajar_out.png", "",
"/common/img/ico_bajar_out.png", "icono",
downButtonListener);
result.setDisabled(true);
} else {
result = createButton("/common/img/ico_bajar1.png",
_("Move down"), "/common/img/ico_bajar.png", "icono",
downButtonListener);
}
return result;
}
protected Button createUpButton(final Treeitem item, final T element) {
EventListener upButtonListener = new EventListener() {
@Override
public void onEvent(Event event) {
up(element);
}
};
Button result;
if (isPredicateApplied() || isFirstItem(element) || readOnly) {
result = createButton("/common/img/ico_subir_out.png", "",
"/common/img/ico_subir_out.png", "icono",
upButtonListener);
result.setDisabled(true);
} else {
result = createButton("/common/img/ico_subir1.png",
_("Move up"), "/common/img/ico_subir.png", "icono",
upButtonListener);
}
return result;
}
protected Button createUnindentButton(final Treeitem item,
final T element) {
EventListener unindentListener = new EventListener() {
@Override
public void onEvent(Event event) {
unindent(element);
}
};
final Button result;
if (isPredicateApplied() || isFirstLevelElement(item) || readOnly) {
result = createButton("/common/img/ico_izq_out.png", "",
"/common/img/ico_izq_out.png", "icono",
unindentListener);
result.setDisabled(true);
} else {
result = createButton("/common/img/ico_izq1.png",
_("Unindent"), "/common/img/ico_izq.png", "icono",
unindentListener);
}
return result;
}
protected Button createIndentButton(final Treeitem item, final T element) {
EventListener indentListener = new EventListener() {
@Override
public void onEvent(Event event) {
indent(element);
}
};
final Button result;
if (isPredicateApplied() || isFirstItem(element) || readOnly) {
result = createButton("/common/img/ico_derecha_out.png", "",
"/common/img/ico_derecha_out.png", "icono",
indentListener);
result.setDisabled(true);
} else {
result = createButton("/common/img/ico_derecha1.png",
_("Indent"), "/common/img/ico_derecha.png", "icono",
indentListener);
}
return result;
}
protected Button createRemoveButton(final T currentElement) { protected Button createRemoveButton(final T currentElement) {
EventListener removeListener = new EventListener() { EventListener removeListener = new EventListener() {
@Override @Override
@ -1239,6 +1188,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
public void doTry() { public void doTry() {
} }
} }
public void setColumns(List<Column> columns) { public void setColumns(List<Column> columns) {
@ -1286,17 +1236,40 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
/** /**
* Disable control buttons (new, up, down, indent, unindent, delete) * Disable control buttons (new, up, down, indent, unindent, delete)
*/ */
public void updateControlButtons(Event event) { public void updateControlButtons() {
updateControlButtons((Tree) event.getTarget()); T element = getSelectedNode();
} if (element == null) {
public void updateControlButtons(Tree tree) {
final Treeitem item = tree.getSelectedItem();
if (item == null) {
resetControlButtons(); resetControlButtons();
return; return;
} }
btnNew.setDisabled(false); Treeitem item = tree.getSelectedItem();
btnNew.setDisabled(isNewButtonDisabled()
|| element.isUpdatedFromTimesheets());
btnNewFromTemplate.setDisabled(isNewButtonDisabled()
|| element.isUpdatedFromTimesheets());
boolean disabled = readOnly || isPredicateApplied();
downButton.setDisabled(disabled || isLastItem(element));
upButton.setDisabled(disabled || isFirstItem(element));
disabled |= element.isUpdatedFromTimesheets();
leftButton.setDisabled(disabled
|| isFirstLevelElement(item)
|| element.getParent().isUpdatedFromTimesheets());
boolean previousSiblingIsUpdatedFromTimesheets = false;
try {
Treeitem previousItem = (Treeitem) item.getParent()
.getChildren().get(item.getIndex() - 1);
T previousSibling = type.cast(previousItem.getValue());
previousSiblingIsUpdatedFromTimesheets = previousSibling
.isUpdatedFromTimesheets();
} catch (IndexOutOfBoundsException e) {
// Do nothing
}
rightButton.setDisabled(disabled || isFirstItem(element)
|| previousSiblingIsUpdatedFromTimesheets);
} }
protected abstract boolean isPredicateApplied(); protected abstract boolean isPredicateApplied();

View file

@ -431,17 +431,39 @@ public class PersonalTimesheetModel implements IPersonalTimesheetModel {
// saved as it will not be possible to find it later with // saved as it will not be possible to find it later with
// WorkReportDAO.getPersonalTimesheetWorkReport() method. // WorkReportDAO.getPersonalTimesheetWorkReport() method.
} else { } else {
Set<WorkReportLine> deletedWorkReportLinesSet = removeWorkReportLinesWithEffortZero();
Set<OrderElement> orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(
workReport.getWorkReportLines(),
deletedWorkReportLinesSet);
sumChargedEffortDAO
.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(deletedWorkReportLinesSet);
sumChargedEffortDAO sumChargedEffortDAO
.updateRelatedSumChargedEffortWithWorkReportLineSet(workReport .updateRelatedSumChargedEffortWithWorkReportLineSet(workReport
.getWorkReportLines()); .getWorkReportLines());
workReport.generateWorkReportLineCodes(entitySequenceDAO workReport.generateWorkReportLineCodes(entitySequenceDAO
.getNumberOfDigitsCode(EntityNameEnum.WORK_REPORT)); .getNumberOfDigitsCode(EntityNameEnum.WORK_REPORT));
workReportDAO.save(workReport); workReportDAO.save(workReport);
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
} }
resetModifiedFields(); resetModifiedFields();
} }
private Set<WorkReportLine> removeWorkReportLinesWithEffortZero() {
Set<WorkReportLine> toRemove = new HashSet<WorkReportLine>();
for (WorkReportLine line : workReport.getWorkReportLines()) {
if (line.getEffort().isZero()) {
toRemove.add(line);
}
}
for (WorkReportLine line : toRemove) {
workReport.removeWorkReportLine(line);
}
return toRemove;
}
private void resetModifiedFields() { private void resetModifiedFields() {
modified = false; modified = false;
modifiedMap = new HashMap<OrderElement, Set<LocalDate>>(); modifiedMap = new HashMap<OrderElement, Set<LocalDate>>();

View file

@ -245,4 +245,10 @@ public interface IWorkReportModel extends IIntegrationEntityModel {
WorkReportLine getFirstWorkReportLine(); WorkReportLine getFirstWorkReportLine();
/**
* Checks if an {@link OrderElement} is finished or not in any
* {@link WorkReportLine} of this report or other report.
*/
boolean isFinished(OrderElement orderElement);
} }

View file

@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.hibernate.validator.InvalidValue; import org.hibernate.validator.InvalidValue;
@ -54,6 +55,8 @@ import org.libreplan.web.common.Level;
import org.libreplan.web.common.MessagesForUser; import org.libreplan.web.common.MessagesForUser;
import org.libreplan.web.common.OnlyOneVisible; import org.libreplan.web.common.OnlyOneVisible;
import org.libreplan.web.common.Util; import org.libreplan.web.common.Util;
import org.libreplan.web.common.Util.Getter;
import org.libreplan.web.common.Util.Setter;
import org.libreplan.web.common.components.Autocomplete; import org.libreplan.web.common.components.Autocomplete;
import org.libreplan.web.common.components.NewDataSortableColumn; import org.libreplan.web.common.components.NewDataSortableColumn;
import org.libreplan.web.common.components.NewDataSortableGrid; import org.libreplan.web.common.components.NewDataSortableGrid;
@ -73,6 +76,7 @@ import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SelectEvent; import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button; import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Column; import org.zkoss.zul.Column;
import org.zkoss.zul.Columns; import org.zkoss.zul.Columns;
import org.zkoss.zul.Comboitem; import org.zkoss.zul.Comboitem;
@ -273,7 +277,9 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
for (InvalidValue invalidValue : e.getInvalidValues()) { for (InvalidValue invalidValue : e.getInvalidValues()) {
Object value = invalidValue.getBean(); Object value = invalidValue.getBean();
if (value instanceof WorkReport) { if (value instanceof WorkReport) {
validateWorkReport(); if (validateWorkReport()) {
messagesForUser.showInvalidValues(e);
}
} }
if (value instanceof WorkReportLine) { if (value instanceof WorkReportLine) {
WorkReportLine workReportLine = (WorkReportLine) invalidValue.getBean(); WorkReportLine workReportLine = (WorkReportLine) invalidValue.getBean();
@ -328,6 +334,7 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
_("cannot be empty")); _("cannot be empty"));
return false; return false;
} }
return true; return true;
} }
@ -452,6 +459,16 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
} }
return false; return false;
} }
if (!workReportLine.checkConstraintOrderElementFinishedInAnotherWorkReport()) {
Checkbox checkboxFinished = getFinished(row);
if (checkboxFinished != null) {
String message = _("task is already marked as finished in another timesheet");
showInvalidMessage(checkboxFinished, message);
}
return false;
}
return true; return true;
} }
@ -466,7 +483,7 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
*/ */
private Timebox getTimeboxFinish(Row row) { private Timebox getTimeboxFinish(Row row) {
try { try {
int position = row.getChildren().size() - 5; int position = row.getChildren().size() - 6;
return (Timebox) row.getChildren().get(position); return (Timebox) row.getChildren().get(position);
} catch (Exception e) { } catch (Exception e) {
return null; return null;
@ -480,7 +497,7 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
*/ */
private Timebox getTimeboxStart(Row row) { private Timebox getTimeboxStart(Row row) {
try { try {
int position = row.getChildren().size() - 6; int position = row.getChildren().size() - 7;
return (Timebox) row.getChildren().get(position); return (Timebox) row.getChildren().get(position);
} catch (Exception e) { } catch (Exception e) {
return null; return null;
@ -494,13 +511,28 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
*/ */
private Listbox getTypeOfHours(Row row) { private Listbox getTypeOfHours(Row row) {
try { try {
int position = row.getChildren().size() - 3; int position = row.getChildren().size() - 4;
return (Listbox) row.getChildren().get(position); return (Listbox) row.getChildren().get(position);
} catch (Exception e) { } catch (Exception e) {
return null; return null;
} }
} }
/**
* Locates {@link Checkbox} finished in {@link Row}
* @param row
* @return
*/
private Checkbox getFinished(Row row) {
try {
int position = row.getChildren().size() - 3;
return (Checkbox) row.getChildren().get(position);
} catch (Exception e) {
return null;
}
}
/** /**
* Locates {@link Texbox} code in {@link Row} * Locates {@link Texbox} code in {@link Row}
* @param row * @param row
@ -523,7 +555,7 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
*/ */
private Textbox getEffort(Row row) { private Textbox getEffort(Row row) {
try { try {
int position = row.getChildren().size() - 4; int position = row.getChildren().size() - 5;
return (Textbox) row.getChildren().get(position); return (Textbox) row.getChildren().get(position);
} catch (Exception e) { } catch (Exception e) {
return null; return null;
@ -815,7 +847,12 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
columnHoursType.setLabel(_("Hours type")); columnHoursType.setLabel(_("Hours type"));
columnHoursType.setSclass("hours-type-column"); columnHoursType.setSclass("hours-type-column");
columns.appendChild(columnHoursType); columns.appendChild(columnHoursType);
NewDataSortableColumn columnFinsihed = new NewDataSortableColumn();
columnFinsihed.setLabel(_("Done"));
columnFinsihed.setSclass("finished-column");
columnFinsihed.setTooltiptext(_("Task finished"));
NewDataSortableColumn columnCode = new NewDataSortableColumn(); NewDataSortableColumn columnCode = new NewDataSortableColumn();
columns.appendChild(columnFinsihed);
columnCode.setLabel(_("Code")); columnCode.setLabel(_("Code"));
columnCode.setSclass("code-column"); columnCode.setSclass("code-column");
columns.appendChild(columnCode); columns.appendChild(columnCode);
@ -1197,6 +1234,29 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
row.appendChild(code); row.appendChild(code);
} }
private void appendFinished(final Row row) {
final WorkReportLine line = (WorkReportLine) row.getValue();
Checkbox finished = Util.bind(new Checkbox(), new Getter<Boolean>() {
@Override
public Boolean get() {
return line.isFinished();
}
}, new Setter<Boolean>() {
@Override
public void set(Boolean value) {
line.setFinished(BooleanUtils.isTrue(value));
}
});
if (!line.isFinished()
&& workReportModel.isFinished(line.getOrderElement())) {
finished.setDisabled(true);
}
row.appendChild(finished);
}
/** /**
* Append a delete {@link Button} to {@link Row} * Append a delete {@link Button} to {@link Row}
* *
@ -1306,6 +1366,7 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
appendEffortDuration(row); appendEffortDuration(row);
appendHoursType(row); appendHoursType(row);
appendFinished(row);
appendCode(row); appendCode(row);
appendDeleteButton(row); appendDeleteButton(row);
} }

View file

@ -34,6 +34,7 @@ import java.util.Set;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.libreplan.business.common.IntegrationEntity; import org.libreplan.business.common.IntegrationEntity;
import org.libreplan.business.common.Util;
import org.libreplan.business.common.daos.IConfigurationDAO; import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.entities.EntityNameEnum; import org.libreplan.business.common.entities.EntityNameEnum;
import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.common.exceptions.InstanceNotFoundException;
@ -51,6 +52,7 @@ import org.libreplan.business.resources.daos.IWorkerDAO;
import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.Worker; import org.libreplan.business.resources.entities.Worker;
import org.libreplan.business.workreports.daos.IWorkReportDAO; import org.libreplan.business.workreports.daos.IWorkReportDAO;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.daos.IWorkReportTypeDAO; import org.libreplan.business.workreports.daos.IWorkReportTypeDAO;
import org.libreplan.business.workreports.entities.WorkReport; import org.libreplan.business.workreports.entities.WorkReport;
import org.libreplan.business.workreports.entities.WorkReportLabelTypeAssigment; import org.libreplan.business.workreports.entities.WorkReportLabelTypeAssigment;
@ -86,6 +88,9 @@ public class WorkReportModel extends IntegrationEntityModel implements
@Autowired @Autowired
private IWorkReportDAO workReportDAO; private IWorkReportDAO workReportDAO;
@Autowired
private IWorkReportLineDAO workReportLineDAO;
@Autowired @Autowired
private IOrderElementDAO orderElementDAO; private IOrderElementDAO orderElementDAO;
@ -274,12 +279,17 @@ public class WorkReportModel extends IntegrationEntityModel implements
@Override @Override
@Transactional @Transactional
public void confirmSave() throws ValidationException { public void confirmSave() throws ValidationException {
Set<OrderElement> orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(
workReport.getWorkReportLines(),
deletedWorkReportLinesSet);
sumChargedEffortDAO.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(deletedWorkReportLinesSet); sumChargedEffortDAO.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(deletedWorkReportLinesSet);
sumChargedEffortDAO sumChargedEffortDAO
.updateRelatedSumChargedEffortWithWorkReportLineSet(workReport .updateRelatedSumChargedEffortWithWorkReportLineSet(workReport
.getWorkReportLines()); .getWorkReportLines());
workReportDAO.save(workReport); workReportDAO.save(workReport);
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
} }
@Override @Override
@ -412,10 +422,14 @@ public class WorkReportModel extends IntegrationEntityModel implements
//before deleting the report, update OrderElement.SumChargedHours //before deleting the report, update OrderElement.SumChargedHours
try { try {
workReportDAO.reattach(workReport); workReportDAO.reattach(workReport);
Set<OrderElement> orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(null,
workReport.getWorkReportLines());
sumChargedEffortDAO sumChargedEffortDAO
.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(workReport .updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(workReport
.getWorkReportLines()); .getWorkReportLines());
workReportDAO.remove(workReport.getId()); workReportDAO.remove(workReport.getId());
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
} catch (InstanceNotFoundException e) { } catch (InstanceNotFoundException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -644,4 +658,25 @@ public class WorkReportModel extends IntegrationEntityModel implements
return workReport.getWorkReportLines().iterator().next(); return workReport.getWorkReportLines().iterator().next();
} }
@Override
@Transactional(readOnly = true)
public boolean isFinished(OrderElement orderElement) {
for (WorkReportLine line : workReport.getWorkReportLines()) {
if (line.isFinished()
&& Util.equals(line.getOrderElement(), orderElement)) {
return true;
}
}
List<WorkReportLine> lines = workReportLineDAO
.findByOrderElementNotInWorkReportAnotherTransaction(
orderElement, workReport);
for (WorkReportLine line : lines) {
if (line.isFinished()) {
return true;
}
}
return false;
}
} }

View file

@ -143,6 +143,7 @@ public abstract class GenericRESTService<E extends IntegrationEntity,
entity.validate(); entity.validate();
beforeSaving(entity); beforeSaving(entity);
entityDAO.saveWithoutValidating(entity); entityDAO.saveWithoutValidating(entity);
afterSaving(entity);
return null; return null;
@ -155,12 +156,23 @@ public abstract class GenericRESTService<E extends IntegrationEntity,
} }
/** /**
* it adds operations that must be done before saving. * It allows to add operations that must be done before saving.
*
* Default implementation is empty.
*/ */
protected void beforeSaving(E entity) { protected void beforeSaving(E entity) {
} }
/**
* It allows to add operations that must be done after saving.
*
* Default implementation is empty.
*/
protected void afterSaving(E entity) {
}
/** /**
* It creates an entity from a DTO. * It creates an entity from a DTO.
* *

View file

@ -22,7 +22,9 @@
package org.libreplan.ws.workreports.impl; package org.libreplan.ws.workreports.impl;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE; import javax.ws.rs.DELETE;
@ -37,8 +39,8 @@ import javax.ws.rs.core.Response.Status;
import org.libreplan.business.common.daos.IIntegrationEntityDAO; import org.libreplan.business.common.daos.IIntegrationEntityDAO;
import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.orders.daos.IOrderElementDAO;
import org.libreplan.business.orders.daos.ISumChargedEffortDAO; import org.libreplan.business.orders.daos.ISumChargedEffortDAO;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.workreports.daos.IWorkReportDAO; import org.libreplan.business.workreports.daos.IWorkReportDAO;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO; import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.entities.WorkReport; import org.libreplan.business.workreports.entities.WorkReport;
@ -64,15 +66,14 @@ public class WorkReportServiceREST extends
GenericRESTService<WorkReport, WorkReportDTO> implements GenericRESTService<WorkReport, WorkReportDTO> implements
IWorkReportService { IWorkReportService {
private Set<OrderElement> orderElements;
@Autowired @Autowired
private IWorkReportDAO workReportDAO; private IWorkReportDAO workReportDAO;
@Autowired @Autowired
private IWorkReportLineDAO workReportLineDAO; private IWorkReportLineDAO workReportLineDAO;
@Autowired
private IOrderElementDAO orderElementDAO;
@Autowired @Autowired
private ISumChargedEffortDAO sumChargedEffortDAO; private ISumChargedEffortDAO sumChargedEffortDAO;
@ -120,11 +121,19 @@ public class WorkReportServiceREST extends
@Override @Override
protected void beforeSaving(WorkReport entity) { protected void beforeSaving(WorkReport entity) {
orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(
entity.getWorkReportLines(), null);
sumChargedEffortDAO sumChargedEffortDAO
.updateRelatedSumChargedEffortWithWorkReportLineSet(entity .updateRelatedSumChargedEffortWithWorkReportLineSet(entity
.getWorkReportLines()); .getWorkReportLines());
} }
@Override
protected void afterSaving(WorkReport entity) {
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
}
@Override @Override
@GET @GET
@Path("/{code}/") @Path("/{code}/")
@ -140,10 +149,14 @@ public class WorkReportServiceREST extends
public Response removeWorkReport(@PathParam("code") String code) { public Response removeWorkReport(@PathParam("code") String code) {
try { try {
WorkReport workReport = workReportDAO.findByCode(code); WorkReport workReport = workReportDAO.findByCode(code);
Set<OrderElement> orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(null,
workReport.getWorkReportLines());
sumChargedEffortDAO sumChargedEffortDAO
.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(workReport .updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(workReport
.getWorkReportLines()); .getWorkReportLines());
workReportDAO.remove(workReport.getId()); workReportDAO.remove(workReport.getId());
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
return Response.ok().build(); return Response.ok().build();
} catch (InstanceNotFoundException e) { } catch (InstanceNotFoundException e) {
return Response.status(Status.NOT_FOUND).build(); return Response.status(Status.NOT_FOUND).build();
@ -157,10 +170,14 @@ public class WorkReportServiceREST extends
public Response removeWorkReportLine(@PathParam("code") String code) { public Response removeWorkReportLine(@PathParam("code") String code) {
try { try {
WorkReportLine workReportLine = workReportLineDAO.findByCode(code); WorkReportLine workReportLine = workReportLineDAO.findByCode(code);
Set<OrderElement> orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(null,
Collections.singleton(workReportLine));
sumChargedEffortDAO sumChargedEffortDAO
.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(new HashSet<WorkReportLine>( .updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(new HashSet<WorkReportLine>(
Arrays.asList(workReportLine))); Arrays.asList(workReportLine)));
workReportLineDAO.remove(workReportLine.getId()); workReportLineDAO.remove(workReportLine.getId());
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
return Response.ok().build(); return Response.ok().build();
} catch (InstanceNotFoundException e) { } catch (InstanceNotFoundException e) {
return Response.status(Status.NOT_FOUND).build(); return Response.status(Status.NOT_FOUND).build();

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

View file

@ -82,7 +82,7 @@
<vbox width="100%"> <vbox width="100%">
<tree id="tree" hflex="1" multiple="false" droppable="true" <tree id="tree" hflex="1" multiple="false" droppable="true"
onDrop="treeController.move(self, event.dragged)" onDrop="treeController.move(self, event.dragged)"
onSelect="treeController.updateControlButtons(event)" onSelect="treeController.updateControlButtons()"
mold="paging" pageSize="25" mold="paging" pageSize="25"
sclass="orderTree" sclass="orderTree"
zclass="z-dottree"> zclass="z-dottree">

View file

@ -274,6 +274,24 @@ div.box.limiting-unassigned {
margin-top: 1px; margin-top: 1px;
} }
.timesheet-date-mark {
color: #F21CFF;
font-size: 8px;
font-weight: bold;
/* By default marks are hidden */
display: none;
}
.first-timesheet-date {
position: absolute;
margin-top: -19px;
}
.last-timesheet-date {
position: absolute;
margin-top: -19px;
}
.completion2 { .completion2 {
width: 0%; width: 0%;
height: 6px; height: 6px;

View file

@ -64,7 +64,7 @@
<hbox align="bottom" sclass="add-resources-or-criteria"> <hbox align="bottom" sclass="add-resources-or-criteria">
<newAllocationSelectorCombo id="newAllocationSelectorCombo" behaviour="NON_LIMITING"/> <newAllocationSelectorCombo id="newAllocationSelectorCombo" behaviour="NON_LIMITING"/>
<button label="${i18n:_('Add')}" onClick="allocationController.onSelectWorkers(newAllocationSelectorCombo)" <button label="${i18n:_('Add')}" onClick="allocationController.onSelectWorkers(newAllocationSelectorCombo)"
disabled="@{allocationController.anyManual}" /> disabled="@{allocationController.isAnyManualOrTaskUpdatedFromTimesheets}" />
<button id="advancedSearchButton" label="${i18n:_('Advanced search')}" onClick="allocationController.goToAdvancedSearch()" /> <button id="advancedSearchButton" label="${i18n:_('Advanced search')}" onClick="allocationController.goToAdvancedSearch()" />
<checkbox id="extendedViewCheckbox" label="${i18n:_('Extended view')}" <checkbox id="extendedViewCheckbox" label="${i18n:_('Extended view')}"
onCheck="allocationController.onCheckExtendedView()"/> onCheck="allocationController.onCheckExtendedView()"/>

View file

@ -14,7 +14,7 @@
} }
.listWorkReportLines .operations-column { .listWorkReportLines .operations-column {
width: 40px; width: 15px;
} }
.listWorkReportLines .resource-column { .listWorkReportLines .resource-column {
@ -25,6 +25,10 @@
width: 150px; width: 150px;
} }
.listWorkReportLines .finished-column {
width: 20px;
}
.listWorkReportLines .order-code-column { .listWorkReportLines .order-code-column {
width: 180px; width: 180px;
} }