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) {
// Comparison through icon as name is internationalized
if (c.getCommand().getImage()
.equals("/common/img/ico_reassign.png")) {
if (plannerToolbar.getChildren().isEmpty()) {
if (c.getCommand().isPlannerCommand()) {
// FIXME Avoid hard-coding the number of planner commands
// At this moment we have 2 planner commands: reassign and adapt
// planning
if (plannerToolbar.getChildren().size() < 2) {
plannerToolbar.appendChild(c.toButton());
}
} 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.PropertyChangeListener;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang.Validate;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Duration;
import org.joda.time.LocalDate;
import org.zkoss.ganttz.adapters.IDisabilityConfiguration;
import org.zkoss.ganttz.data.GanttDate;
@ -517,8 +520,34 @@ public class TaskComponent extends Div implements AfterCompose {
this.task.getHoursAdvanceEndDate()) + "px";
response(null, new AuInvoke(this, "resizeCompletionAdvance",
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 {
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;
}
@Override
public boolean isPlannerCommand() {
return false;
}
}
private static class NullCommandOnTask<T> implements ICommandOnTask<T> {

View file

@ -308,4 +308,19 @@ public class DefaultFundamentalProperties implements ITaskFundamentalProperties
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();
boolean isUpdatedFromTimesheets();
Date getFirstTimesheetDate();
Date getLastTimesheetDate();
}

View file

@ -548,6 +548,21 @@ public abstract class Task implements ITaskFundamentalProperties {
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() {
fundamentalPropertiesListeners.firePropertyChange("beginDate", null,
getBeginDate());

View file

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

View file

@ -39,4 +39,10 @@ public interface ICommand<T> {
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){
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){
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="completion"></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">',
this.getTooltipText(),

View file

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

View file

@ -86,6 +86,8 @@ public class AdvanceType extends BaseEntity implements IHumanIdentifiable{
private IAdvanceTypeDAO avanceTypeDAO = Registry.getAdvanceTypeDao();
private boolean readOnly = false;
/**
* Constructor for hibernate. Do not use!
*/
@ -271,4 +273,12 @@ public class AdvanceType extends BaseEntity implements IHumanIdentifiable{
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();
}
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);
/**
* 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;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@ -66,6 +70,9 @@ public class SumChargedEffortDAO extends
@Autowired
private IOrderDAO orderDAO;
@Autowired
private IOrderElementDAO orderElementDAO;
private Map<OrderElement, SumChargedEffort> mapSumChargedEfforts;
@Override
@ -114,6 +121,7 @@ public class SumChargedEffortDAO extends
forceLoadParents(parent);
}
}
});
previousEffort = previous.getFirst();
@ -149,9 +157,6 @@ public class SumChargedEffortDAO extends
private void addDirectChargedEffort(OrderElement orderElement,
EffortDuration effort) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
sumChargedEffort.addDirectChargedEffort(effort);
save(sumChargedEffort);
@ -163,9 +168,6 @@ public class SumChargedEffortDAO extends
EffortDuration effort) {
if (orderElement != null) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
sumChargedEffort.addIndirectChargedEffort(effort);
save(sumChargedEffort);
@ -231,6 +233,9 @@ public class SumChargedEffortDAO extends
.get(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = findByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
mapSumChargedEfforts.put(orderElement, sumChargedEffort);
}
return sumChargedEffort;
@ -251,6 +256,7 @@ public class SumChargedEffortDAO extends
resetMapSumChargedEfforts();
resetSumChargedEffort(order);
calculateDirectChargedEffort(order);
calculateTimesheetData(order);
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
@ -258,9 +264,6 @@ public class SumChargedEffortDAO extends
private void resetSumChargedEffort(OrderElement orderElement) {
SumChargedEffort sumChargedEffort = getByOrderElement(orderElement);
if (sumChargedEffort == null) {
sumChargedEffort = SumChargedEffort.create(orderElement);
}
sumChargedEffort.reset();
for (OrderElement each : orderElement.getChildren()) {
@ -281,4 +284,159 @@ public class SumChargedEffortDAO extends
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();
}
public TaskElement getTaskElement() {
TaskSource taskSource = getTaskSource();
if (taskSource == null) {
return null;
}
return taskSource.getTask();
}
public Set<TaskElement> getTaskElements() {
if (getTaskSource() == null) {
return Collections.emptySet();
@ -1586,6 +1594,44 @@ public abstract class OrderElement extends IntegrationEntity implements
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() {
parent = null;
}

View file

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

View file

@ -21,8 +21,12 @@
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.workingday.EffortDuration;
import org.libreplan.business.workreports.entities.WorkReportLine;
/**
* 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 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() {}
private SumChargedEffort(OrderElement orderElement) {
@ -93,6 +108,38 @@ public class SumChargedEffort extends BaseEntity {
public void reset() {
directChargedEffort = 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();
}
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.TreeMap;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -182,6 +183,8 @@ public abstract class TaskElement extends BaseEntity {
private Boolean simplifiedAssignedStatusCalculationEnabled = false;
private Boolean updatedFromTimesheets = false;
public void initializeDatesIfNeeded() {
if (getIntraDayEndDate() == null || getIntraDayStartDate() == null) {
initializeDates();
@ -837,4 +840,12 @@ public abstract class TaskElement extends BaseEntity {
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();
@Override
public boolean isUpdatedFromTimesheets() {
return false;
}
}

View file

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

View file

@ -60,4 +60,6 @@ public interface ITreeNode<T extends ITreeNode<T>> {
*/
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.reports.dtos.WorkReportLineDTO;
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.WorkReportLine;
@ -65,6 +66,14 @@ public interface IWorkReportLineDAO extends
List<WorkReportLine> findByResourceFilteredByDateNotInWorkReport(
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(
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.reports.dtos.WorkReportLineDTO;
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.WorkReportLine;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
@ -146,6 +148,57 @@ public class WorkReportLineDAO extends IntegrationEntityDAO<WorkReportLine>
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")
@Override
public List<WorkReportLine> findByOrderElementAndWorkReports(

View file

@ -36,6 +36,7 @@ import org.hibernate.validator.Valid;
import org.joda.time.LocalDate;
import org.libreplan.business.common.IntegrationEntity;
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.PersonalTimesheetsPeriodicityEnum;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
@ -538,4 +539,20 @@ public class WorkReport extends IntegrationEntity implements
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.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
@ -78,6 +79,8 @@ public class WorkReportLine extends IntegrationEntity implements Comparable,
private TypeOfWorkHours typeOfWorkHours;
private Boolean finished = false;
/**
* Constructor for hibernate. Do not use!
*/
@ -552,4 +555,31 @@ public class WorkReportLine extends IntegrationEntity implements Comparable,
: 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>
</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">
<comment>Updating status values in order_table</comment>
<update tableName="order_table">

View file

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

View file

@ -272,6 +272,14 @@
<property name="indirectChargedEffort" access="field"
column="indirect_charged_effort"
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 name="SumExpenses" table="sum_expenses">

View file

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

View file

@ -162,6 +162,8 @@
</composite-element>
</set>
<property name="finished" />
</class>
<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.zul.Bandbox;
import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Comboitem;
import org.zkoss.zul.Datebox;
@ -67,7 +68,6 @@ import org.zkoss.zul.Radio;
import org.zkoss.zul.Row;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.Timebox;
import org.zkoss.zul.api.Checkbox;
import org.zkoss.zul.api.Column;
/**
@ -446,7 +446,7 @@ public class Util {
* The {@link Setter} interface that will implement a set method.
* @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) {
checkBox.setChecked(getter.get());
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.Getter;
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

View file

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

View file

@ -366,7 +366,10 @@ public class ManageOrderElementAdvancesController extends
if (advance.getAdvanceType() != null) {
isQualityForm = manageOrderElementAdvancesModel
.isQualityForm(advance);
if (manageOrderElementAdvancesModel
readOnlyAdvance = manageOrderElementAdvancesModel
.isReadOnly(advance);
if (!readOnlyAdvance
&& manageOrderElementAdvancesModel
.isSubcontratedAdvanceTypeAndSubcontratedTask(advance)) {
readOnlyAdvance = true;
}
@ -375,12 +378,12 @@ public class ManageOrderElementAdvancesController extends
if ((advance instanceof DirectAdvanceAssignment)
&& ((DirectAdvanceAssignment) advance)
.getAdvanceMeasurements().isEmpty()
&& !isQualityForm) {
&& !isQualityForm && !readOnlyAdvance) {
appendComboboxAdvanceType(listItem);
} else {
appendLabelAdvanceType(listItem);
}
appendDecimalBoxMaxValue(listItem, isQualityForm);
appendDecimalBoxMaxValue(listItem, isQualityForm || readOnlyAdvance);
appendDecimalBoxValue(listItem);
appendLabelPercentage(listItem);
appendDateBoxDate(listItem);
@ -401,7 +404,8 @@ public class ManageOrderElementAdvancesController extends
for(AdvanceType advanceType : listAdvanceType){
if (!advanceType.getUnitName().equals(
PredefinedAdvancedTypes.CHILDREN.getTypeName())
&& !advanceType.isQualityForm()) {
&& !advanceType.isQualityForm()
&& !advanceType.isReadOnly()) {
Comboitem comboItem = new Comboitem();
comboItem.setValue(advanceType);
comboItem.setLabel(advanceType.getUnitName());
@ -461,7 +465,7 @@ public class ManageOrderElementAdvancesController extends
}
private void appendDecimalBoxMaxValue(final Listitem listItem,
boolean isQualityForm) {
boolean isQualityFormOrReadOnly) {
final AdvanceAssignment advanceAssignment = (AdvanceAssignment) listItem
.getValue();
final Decimalbox maxValue = new Decimalbox();
@ -469,7 +473,7 @@ public class ManageOrderElementAdvancesController extends
final DirectAdvanceAssignment directAdvanceAssignment;
if ((advanceAssignment instanceof IndirectAdvanceAssignment)
|| isQualityForm
|| isQualityFormOrReadOnly
|| (advanceAssignment.getAdvanceType() != null && advanceAssignment
.getAdvanceType().getPercentage())
|| manageOrderElementAdvancesModel
@ -708,6 +712,11 @@ public class ManageOrderElementAdvancesController extends
addMeasurementButton.setDisabled(true);
addMeasurementButton
.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) {
addMeasurementButton.setDisabled(true);
addMeasurementButton
@ -739,6 +748,11 @@ public class ManageOrderElementAdvancesController extends
removeButton.setDisabled(true);
removeButton
.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) {
removeButton.setDisabled(true);
removeButton
@ -1219,6 +1233,11 @@ public class ManageOrderElementAdvancesController extends
removeButton.setDisabled(true);
removeButton
.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()) {
removeButton.setDisabled(true);
removeButton

View file

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

View file

@ -1203,7 +1203,8 @@ _(
@Override
public boolean isFixed() {
return taskElement.isLimitingAndHasDayAssignments()
|| taskElement.hasConsolidations();
|| taskElement.hasConsolidations()
|| taskElement.isUpdatedFromTimesheets();
}
@Override
@ -1222,6 +1223,29 @@ _(
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

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

View file

@ -107,7 +107,8 @@ public class AllocationConfiguration extends HtmlMacroComponent {
.getCalculatedValue())) {
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() {
return new ArrayList<AllocationRow>(currentRows);
}
public boolean isTaskUpdatedFromTimesheets() {
return task.isUpdatedFromTimesheets();
}
}

View file

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

View file

@ -602,7 +602,7 @@ public class ResourceAllocationController extends GenericForwardComposer {
// On click delete button
Button deleteButton = appendDeleteButton(row);
deleteButton.setDisabled(isAnyManual());
deleteButton.setDisabled(isAnyManualOrTaskUpdatedFromTimesheets());
formBinder.setDeleteButtonFor(data, deleteButton);
deleteButton.addEventListener("onClick", new EventListener() {
@ -671,8 +671,12 @@ public class ResourceAllocationController extends GenericForwardComposer {
return formBinder != null && formBinder.isAnyNotFlat();
}
public boolean isAnyManual() {
return formBinder != null && formBinder.isAnyManual();
public boolean isAnyManualOrTaskUpdatedFromTimesheets() {
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.web.calendars.BaseCalendarModel;
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.IAdvanceAssignmentPlanningCommand;
import org.libreplan.web.planner.allocation.IAdvancedAllocationCommand;
@ -222,6 +223,9 @@ public class OrderPlanningModel implements IOrderPlanningModel {
@Autowired
private IReassignCommand reassignCommand;
@Autowired
private IAdaptPlanningCommand adaptPlanningCommand;
@Autowired
private IResourceAllocationCommand resourceAllocationCommand;
@ -332,6 +336,7 @@ public class OrderPlanningModel implements IOrderPlanningModel {
configuration.addGlobalCommand(buildReassigningCommand());
configuration.addGlobalCommand(buildCancelEditionCommand());
configuration.addGlobalCommand(buildAdaptPlanningCommand());
NullSeparatorCommandOnTask<TaskElement> separator = new NullSeparatorCommandOnTask<TaskElement>();
@ -1055,6 +1060,11 @@ public class OrderPlanningModel implements IOrderPlanningModel {
return reassignCommand;
}
private ICommand<TaskElement> buildAdaptPlanningCommand() {
adaptPlanningCommand.setState(planningState);
return adaptPlanningCommand;
}
private ICommand<TaskElement> buildCancelEditionCommand() {
return new ICommand<TaskElement>() {
@ -1097,6 +1107,11 @@ public class OrderPlanningModel implements IOrderPlanningModel {
return false;
}
@Override
public boolean isPlannerCommand() {
return false;
}
};
}

View file

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

View file

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

View file

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

View file

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

View file

@ -116,6 +116,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
getModel().indent(element);
filterByPredicateIfAny();
updateControlButtons();
}
public TreeModel getTreeModel() {
@ -140,6 +141,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
getModel().unindent(element);
filterByPredicateIfAny();
updateControlButtons();
}
public void up() {
@ -153,6 +155,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
getModel().up(element);
filterByPredicateIfAny();
updateControlButtons();
}
public void down() {
@ -165,6 +168,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
getModel().down(element);
filterByPredicateIfAny();
updateControlButtons();
}
public T getSelectedNode() {
@ -184,8 +188,15 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree);
Treerow from = (Treerow) dragged;
T fromNode = type.cast(((Treeitem) from.getParent()).getValue());
T fromNode = null;
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) {
getModel().moveToRoot(fromNode);
}
@ -354,6 +365,15 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
private List<Column> columns;
private Button btnNewFromTemplate;
private Button downButton;
private Button upButton;
private Button leftButton;
private Button rightButton;
protected TreeViewStateSnapshot getSnapshotOfOpenedNodes() {
return viewStateSnapshot;
@ -361,6 +381,13 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
private void resetControlButtons() {
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();
@ -692,8 +719,8 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
public void render(final Treeitem item, Object data) {
item.setValue(data);
applySnapshot(item);
currentTreeRow = getTreeRowWithoutChildrenFor(item);
final T currentElement = type.cast(data);
currentTreeRow = getTreeRowWithoutChildrenFor(item, currentElement);
createCells(item, currentElement);
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);
// 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();
return result;
}
@ -766,7 +800,8 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
final SchedulingState schedulingState = getSchedulingStateFrom(currentElement);
SchedulingStateToggler schedulingStateToggler = new SchedulingStateToggler(
schedulingState);
schedulingStateToggler.setReadOnly(readOnly);
schedulingStateToggler.setReadOnly(readOnly
|| currentElement.isUpdatedFromTimesheets());
final Treecell cell = addCell(
getDecorationFromState(getSchedulingStateFrom(currentElement)),
schedulingStateToggler);
@ -796,7 +831,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
}
});
schedulingStateToggler.afterCompose();
cell.setDraggable("true");
}
protected abstract SchedulingState getSchedulingStateFrom(
@ -1105,92 +1140,6 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
protected abstract void onDoubleClickForSchedulingStateCell(
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) {
EventListener removeListener = new EventListener() {
@Override
@ -1239,6 +1188,7 @@ public abstract class TreeController<T extends ITreeNode<T>> extends
public void doTry() {
}
}
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)
*/
public void updateControlButtons(Event event) {
updateControlButtons((Tree) event.getTarget());
}
public void updateControlButtons(Tree tree) {
final Treeitem item = tree.getSelectedItem();
if (item == null) {
public void updateControlButtons() {
T element = getSelectedNode();
if (element == null) {
resetControlButtons();
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();

View file

@ -431,17 +431,39 @@ public class PersonalTimesheetModel implements IPersonalTimesheetModel {
// saved as it will not be possible to find it later with
// WorkReportDAO.getPersonalTimesheetWorkReport() method.
} else {
Set<WorkReportLine> deletedWorkReportLinesSet = removeWorkReportLinesWithEffortZero();
Set<OrderElement> orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(
workReport.getWorkReportLines(),
deletedWorkReportLinesSet);
sumChargedEffortDAO
.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(deletedWorkReportLinesSet);
sumChargedEffortDAO
.updateRelatedSumChargedEffortWithWorkReportLineSet(workReport
.getWorkReportLines());
workReport.generateWorkReportLineCodes(entitySequenceDAO
.getNumberOfDigitsCode(EntityNameEnum.WORK_REPORT));
workReportDAO.save(workReport);
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
}
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() {
modified = false;
modifiedMap = new HashMap<OrderElement, Set<LocalDate>>();

View file

@ -245,4 +245,10 @@ public interface IWorkReportModel extends IIntegrationEntityModel {
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.regex.Pattern;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.LogFactory;
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.OnlyOneVisible;
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.NewDataSortableColumn;
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.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Column;
import org.zkoss.zul.Columns;
import org.zkoss.zul.Comboitem;
@ -273,7 +277,9 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
for (InvalidValue invalidValue : e.getInvalidValues()) {
Object value = invalidValue.getBean();
if (value instanceof WorkReport) {
validateWorkReport();
if (validateWorkReport()) {
messagesForUser.showInvalidValues(e);
}
}
if (value instanceof WorkReportLine) {
WorkReportLine workReportLine = (WorkReportLine) invalidValue.getBean();
@ -328,6 +334,7 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
_("cannot be empty"));
return false;
}
return true;
}
@ -452,6 +459,16 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
}
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;
}
@ -466,7 +483,7 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
*/
private Timebox getTimeboxFinish(Row row) {
try {
int position = row.getChildren().size() - 5;
int position = row.getChildren().size() - 6;
return (Timebox) row.getChildren().get(position);
} catch (Exception e) {
return null;
@ -480,7 +497,7 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
*/
private Timebox getTimeboxStart(Row row) {
try {
int position = row.getChildren().size() - 6;
int position = row.getChildren().size() - 7;
return (Timebox) row.getChildren().get(position);
} catch (Exception e) {
return null;
@ -494,13 +511,28 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
*/
private Listbox getTypeOfHours(Row row) {
try {
int position = row.getChildren().size() - 3;
int position = row.getChildren().size() - 4;
return (Listbox) row.getChildren().get(position);
} catch (Exception e) {
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}
* @param row
@ -523,7 +555,7 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
*/
private Textbox getEffort(Row row) {
try {
int position = row.getChildren().size() - 4;
int position = row.getChildren().size() - 5;
return (Textbox) row.getChildren().get(position);
} catch (Exception e) {
return null;
@ -815,7 +847,12 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
columnHoursType.setLabel(_("Hours type"));
columnHoursType.setSclass("hours-type-column");
columns.appendChild(columnHoursType);
NewDataSortableColumn columnFinsihed = new NewDataSortableColumn();
columnFinsihed.setLabel(_("Done"));
columnFinsihed.setSclass("finished-column");
columnFinsihed.setTooltiptext(_("Task finished"));
NewDataSortableColumn columnCode = new NewDataSortableColumn();
columns.appendChild(columnFinsihed);
columnCode.setLabel(_("Code"));
columnCode.setSclass("code-column");
columns.appendChild(columnCode);
@ -1197,6 +1234,29 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
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}
*
@ -1306,6 +1366,7 @@ public class WorkReportCRUDController extends GenericForwardComposer implements
appendEffortDuration(row);
appendHoursType(row);
appendFinished(row);
appendCode(row);
appendDeleteButton(row);
}

View file

@ -34,6 +34,7 @@ import java.util.Set;
import org.apache.commons.lang.Validate;
import org.hibernate.Hibernate;
import org.libreplan.business.common.IntegrationEntity;
import org.libreplan.business.common.Util;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.entities.EntityNameEnum;
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.Worker;
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.entities.WorkReport;
import org.libreplan.business.workreports.entities.WorkReportLabelTypeAssigment;
@ -86,6 +88,9 @@ public class WorkReportModel extends IntegrationEntityModel implements
@Autowired
private IWorkReportDAO workReportDAO;
@Autowired
private IWorkReportLineDAO workReportLineDAO;
@Autowired
private IOrderElementDAO orderElementDAO;
@ -274,12 +279,17 @@ public class WorkReportModel extends IntegrationEntityModel implements
@Override
@Transactional
public void confirmSave() throws ValidationException {
Set<OrderElement> orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(
workReport.getWorkReportLines(),
deletedWorkReportLinesSet);
sumChargedEffortDAO.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(deletedWorkReportLinesSet);
sumChargedEffortDAO
.updateRelatedSumChargedEffortWithWorkReportLineSet(workReport
.getWorkReportLines());
workReportDAO.save(workReport);
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
}
@Override
@ -412,10 +422,14 @@ public class WorkReportModel extends IntegrationEntityModel implements
//before deleting the report, update OrderElement.SumChargedHours
try {
workReportDAO.reattach(workReport);
Set<OrderElement> orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(null,
workReport.getWorkReportLines());
sumChargedEffortDAO
.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(workReport
.getWorkReportLines());
workReportDAO.remove(workReport.getId());
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
} catch (InstanceNotFoundException e) {
throw new RuntimeException(e);
}
@ -644,4 +658,25 @@ public class WorkReportModel extends IntegrationEntityModel implements
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();
beforeSaving(entity);
entityDAO.saveWithoutValidating(entity);
afterSaving(entity);
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) {
}
/**
* 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.
*

View file

@ -22,7 +22,9 @@
package org.libreplan.ws.workreports.impl;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes;
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.exceptions.InstanceNotFoundException;
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.entities.OrderElement;
import org.libreplan.business.workreports.daos.IWorkReportDAO;
import org.libreplan.business.workreports.daos.IWorkReportLineDAO;
import org.libreplan.business.workreports.entities.WorkReport;
@ -64,15 +66,14 @@ public class WorkReportServiceREST extends
GenericRESTService<WorkReport, WorkReportDTO> implements
IWorkReportService {
private Set<OrderElement> orderElements;
@Autowired
private IWorkReportDAO workReportDAO;
@Autowired
private IWorkReportLineDAO workReportLineDAO;
@Autowired
private IOrderElementDAO orderElementDAO;
@Autowired
private ISumChargedEffortDAO sumChargedEffortDAO;
@ -120,11 +121,19 @@ public class WorkReportServiceREST extends
@Override
protected void beforeSaving(WorkReport entity) {
orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(
entity.getWorkReportLines(), null);
sumChargedEffortDAO
.updateRelatedSumChargedEffortWithWorkReportLineSet(entity
.getWorkReportLines());
}
@Override
protected void afterSaving(WorkReport entity) {
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
}
@Override
@GET
@Path("/{code}/")
@ -140,10 +149,14 @@ public class WorkReportServiceREST extends
public Response removeWorkReport(@PathParam("code") String code) {
try {
WorkReport workReport = workReportDAO.findByCode(code);
Set<OrderElement> orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(null,
workReport.getWorkReportLines());
sumChargedEffortDAO
.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(workReport
.getWorkReportLines());
workReportDAO.remove(workReport.getId());
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
return Response.ok().build();
} catch (InstanceNotFoundException e) {
return Response.status(Status.NOT_FOUND).build();
@ -157,10 +170,14 @@ public class WorkReportServiceREST extends
public Response removeWorkReportLine(@PathParam("code") String code) {
try {
WorkReportLine workReportLine = workReportLineDAO.findByCode(code);
Set<OrderElement> orderElements = sumChargedEffortDAO
.getOrderElementsToRecalculateTimsheetDates(null,
Collections.singleton(workReportLine));
sumChargedEffortDAO
.updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(new HashSet<WorkReportLine>(
Arrays.asList(workReportLine)));
workReportLineDAO.remove(workReportLine.getId());
sumChargedEffortDAO.recalculateTimesheetData(orderElements);
return Response.ok().build();
} catch (InstanceNotFoundException e) {
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%">
<tree id="tree" hflex="1" multiple="false" droppable="true"
onDrop="treeController.move(self, event.dragged)"
onSelect="treeController.updateControlButtons(event)"
onSelect="treeController.updateControlButtons()"
mold="paging" pageSize="25"
sclass="orderTree"
zclass="z-dottree">

View file

@ -274,6 +274,24 @@ div.box.limiting-unassigned {
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 {
width: 0%;
height: 6px;

View file

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

View file

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