diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/Planner.java b/ganttzk/src/main/java/org/zkoss/ganttz/Planner.java index 0e84ef16a..08c7a1347 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/Planner.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/Planner.java @@ -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; + } + } diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/TaskComponent.java b/ganttzk/src/main/java/org/zkoss/ganttz/TaskComponent.java index 6321eb29d..ea299a028 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/TaskComponent.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/TaskComponent.java @@ -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")); } } diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/adapters/PlannerConfiguration.java b/ganttzk/src/main/java/org/zkoss/ganttz/adapters/PlannerConfiguration.java index 0931feeac..864d78cc5 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/adapters/PlannerConfiguration.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/adapters/PlannerConfiguration.java @@ -86,6 +86,11 @@ public class PlannerConfiguration implements IDisabilityConfiguration { return false; } + @Override + public boolean isPlannerCommand() { + return false; + } + } private static class NullCommandOnTask implements ICommandOnTask { diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/data/DefaultFundamentalProperties.java b/ganttzk/src/main/java/org/zkoss/ganttz/data/DefaultFundamentalProperties.java index 1d2b23f0a..d0427cb50 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/data/DefaultFundamentalProperties.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/data/DefaultFundamentalProperties.java @@ -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; + } + } diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/data/ITaskFundamentalProperties.java b/ganttzk/src/main/java/org/zkoss/ganttz/data/ITaskFundamentalProperties.java index 21d1673da..aa32e7e16 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/data/ITaskFundamentalProperties.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/data/ITaskFundamentalProperties.java @@ -125,4 +125,10 @@ public interface ITaskFundamentalProperties { public boolean isRoot(); + boolean isUpdatedFromTimesheets(); + + Date getFirstTimesheetDate(); + + Date getLastTimesheetDate(); + } diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/data/Task.java b/ganttzk/src/main/java/org/zkoss/ganttz/data/Task.java index 215497fa3..fcb215e11 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/data/Task.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/data/Task.java @@ -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()); diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/data/TaskLeaf.java b/ganttzk/src/main/java/org/zkoss/ganttz/data/TaskLeaf.java index bf4251c74..55ea35ac3 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/data/TaskLeaf.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/data/TaskLeaf.java @@ -56,7 +56,7 @@ public class TaskLeaf extends Task { @Override public boolean canBeExplicitlyMoved() { return !(isSubcontracted() || isLimitingAndHasDayAssignments() - || hasConsolidations() || isManualAnyAllocation()); + || hasConsolidations() || isManualAnyAllocation() || isUpdatedFromTimesheets()); } } diff --git a/ganttzk/src/main/java/org/zkoss/ganttz/extensions/ICommand.java b/ganttzk/src/main/java/org/zkoss/ganttz/extensions/ICommand.java index d883748b3..186ad8412 100644 --- a/ganttzk/src/main/java/org/zkoss/ganttz/extensions/ICommand.java +++ b/ganttzk/src/main/java/org/zkoss/ganttz/extensions/ICommand.java @@ -39,4 +39,10 @@ public interface ICommand { boolean isDisabled(); + /** + * Describes if a command is for the planner toolbar. Otherwise it'll be + * inserted in the common toolbar. + */ + boolean isPlannerCommand(); + } diff --git a/ganttzk/src/main/resources/web/js/ganttz/TaskComponent.js b/ganttzk/src/main/resources/web/js/ganttz/TaskComponent.js index 86de72579..03e5203ab 100644 --- a/ganttzk/src/main/resources/web/js/ganttz/TaskComponent.js +++ b/ganttzk/src/main/resources/web/js/ganttz/TaskComponent.js @@ -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); }, diff --git a/ganttzk/src/main/resources/web/js/ganttz/mold/task-component.js b/ganttzk/src/main/resources/web/js/ganttz/mold/task-component.js index 3685a791e..c4784599d 100644 --- a/ganttzk/src/main/resources/web/js/ganttz/mold/task-component.js +++ b/ganttzk/src/main/resources/web/js/ganttz/mold/task-component.js @@ -14,6 +14,8 @@ function(out){ out.push('
'); out.push('
'); out.push('
'); + out.push('
|
'); + out.push('
|
'); out.push('
', this.getTooltipText(), diff --git a/libreplan-business/src/main/java/org/libreplan/business/advance/bootstrap/PredefinedAdvancedTypes.java b/libreplan-business/src/main/java/org/libreplan/business/advance/bootstrap/PredefinedAdvancedTypes.java index 26b7eaf3f..c3c45ce5d 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/advance/bootstrap/PredefinedAdvancedTypes.java +++ b/libreplan-business/src/main/java/org/libreplan/business/advance/bootstrap/PredefinedAdvancedTypes.java @@ -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() { diff --git a/libreplan-business/src/main/java/org/libreplan/business/advance/entities/AdvanceType.java b/libreplan-business/src/main/java/org/libreplan/business/advance/entities/AdvanceType.java index 74f476ea4..bbbb320c9 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/advance/entities/AdvanceType.java +++ b/libreplan-business/src/main/java/org/libreplan/business/advance/entities/AdvanceType.java @@ -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; + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/advance/entities/DirectAdvanceAssignment.java b/libreplan-business/src/main/java/org/libreplan/business/advance/entities/DirectAdvanceAssignment.java index 4ef909e66..b44c06c30 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/advance/entities/DirectAdvanceAssignment.java +++ b/libreplan-business/src/main/java/org/libreplan/business/advance/entities/DirectAdvanceAssignment.java @@ -263,4 +263,9 @@ public class DirectAdvanceAssignment extends AdvanceAssignment { return !nonCalculatedConsolidations.isEmpty(); } + public void resetAdvanceMeasurements(AdvanceMeasurement advanceMeasurement) { + advanceMeasurements.clear(); + addAdvanceMeasurements(advanceMeasurement); + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/Util.java b/libreplan-business/src/main/java/org/libreplan/business/common/Util.java new file mode 100644 index 000000000..fe8bbacfb --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/Util.java @@ -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 . + */ + +package org.libreplan.business.common; + +import java.util.Collection; + +/** + * Utilities class.
+ * @author Manuel Rego Casasnovas + */ +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 collection, + BaseEntity entity) { + for (BaseEntity each : collection) { + if (each.getId().equals(entity.getId())) { + return true; + } + } + return false; + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/daos/ISumChargedEffortDAO.java b/libreplan-business/src/main/java/org/libreplan/business/orders/daos/ISumChargedEffortDAO.java index 10301a50a..c17004896 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/daos/ISumChargedEffortDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/daos/ISumChargedEffortDAO.java @@ -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 null as param if you only have one of the sets. + */ + Set getOrderElementsToRecalculateTimsheetDates( + Set workReportLines, + Set deletedWorkReportLinesSet); + + /** + * Recalulates the first and last timesheets dates for each + * {@link OrderElement} in the {@link Set}. + */ + void recalculateTimesheetData(Set orderElements); + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/daos/SumChargedEffortDAO.java b/libreplan-business/src/main/java/org/libreplan/business/orders/daos/SumChargedEffortDAO.java index babaab2cb..44bb46163 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/daos/SumChargedEffortDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/daos/SumChargedEffortDAO.java @@ -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 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 calculateTimesheetDatesAndChildren( + OrderElement orderElement) { + Pair minMax = workReportLineDAO + .findMinAndMaxDatesByOrderElement(orderElement); + + Set minDates = new HashSet(); + Set maxDates = new HashSet(); + + addIfNotNull(minDates, minMax.getFirst()); + addIfNotNull(maxDates, minMax.getSecond()); + + for (OrderElement child : orderElement.getChildren()) { + Pair minMaxChild = calculateTimesheetDatesAndChildren(child); + addIfNotNull(minDates, minMaxChild.getFirst()); + addIfNotNull(maxDates, minMaxChild.getSecond()); + } + + Pair 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 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 getOrderElementsToRecalculateTimsheetDates( + Set workReportLines, + Set deletedWorkReportLines) { + Set orderElements = new HashSet(); + + if (workReportLines != null) { + for (final WorkReportLine workReportLine : workReportLines) { + if (!workReportLine.isNewObject()) { + OrderElement previousOrderElement = transactionService + .runOnAnotherTransaction(new IOnTransaction() { + @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 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 minMax = workReportLineDAO + .findMinAndMaxDatesByOrderElement(orderElement); + + Set minDates = new HashSet(); + Set maxDates = new HashSet(); + + 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 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); + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java index c22f4cc62..fbcd0a21b 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java @@ -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 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; } diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderLineGroup.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderLineGroup.java index b201fe655..faf62a494 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderLineGroup.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderLineGroup.java @@ -158,6 +158,11 @@ public class OrderLineGroup extends OrderElement implements return false; } + @Override + public boolean isUpdatedFromTimesheets() { + return getThis().isUpdatedFromTimesheets(); + } + } public static OrderLineGroup create() { diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffort.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffort.java index 46a8839e9..9dd625c1d 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffort.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffort.java @@ -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 true 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); } } diff --git a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/ResourceAllocation.java b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/ResourceAllocation.java index 5a9852d3f..1eb1c6f6d 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/ResourceAllocation.java +++ b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/ResourceAllocation.java @@ -2253,4 +2253,17 @@ public abstract class ResourceAllocation extends intendedResourcesPerDay = getNonConsolidatedResourcePerDay(); } + public void removeDayAssignmentsBeyondDate(LocalDate date) { + List toRemove = new ArrayList(); + + for (T t : getAssignments()) { + if (t.getDay().compareTo(date) >= 0) { + toRemove.add(t); + } + } + + setOnDayAssignmentRemoval(new DetachDayAssignmentOnRemoval()); + getDayAssignmentsState().removingAssignments(toRemove); + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskElement.java b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskElement.java index 3af1c9817..3d7360085 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskElement.java +++ b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/TaskElement.java @@ -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); + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderElementTemplate.java b/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderElementTemplate.java index c7f04ecc8..9d05919f3 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderElementTemplate.java +++ b/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderElementTemplate.java @@ -620,4 +620,9 @@ public abstract class OrderElementTemplate extends BaseEntity implements public abstract boolean isOrderTemplate(); + @Override + public boolean isUpdatedFromTimesheets() { + return false; + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderLineGroupTemplate.java b/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderLineGroupTemplate.java index 4d126c5e9..f572dfa88 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderLineGroupTemplate.java +++ b/libreplan-business/src/main/java/org/libreplan/business/templates/entities/OrderLineGroupTemplate.java @@ -100,6 +100,11 @@ public class OrderLineGroupTemplate extends OrderElementTemplate implements return false; } + @Override + public boolean isUpdatedFromTimesheets() { + return false; + } + } public static OrderLineGroupTemplate createNew() { diff --git a/libreplan-business/src/main/java/org/libreplan/business/trees/ITreeNode.java b/libreplan-business/src/main/java/org/libreplan/business/trees/ITreeNode.java index f27bb8d54..bd6facd23 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/trees/ITreeNode.java +++ b/libreplan-business/src/main/java/org/libreplan/business/trees/ITreeNode.java @@ -60,4 +60,6 @@ public interface ITreeNode> { */ boolean isEmptyLeaf(); + boolean isUpdatedFromTimesheets(); + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/IWorkReportLineDAO.java b/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/IWorkReportLineDAO.java index 8b37b1874..09ee268d7 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/IWorkReportLineDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/IWorkReportLineDAO.java @@ -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 findByResourceFilteredByDateNotInWorkReport( Resource resource, Date start, Date end, WorkReport workReport); + Pair findMinAndMaxDatesByOrderElement( + OrderElement orderElement); + + List findByOrderElementNotInWorkReportAnotherTransaction( + OrderElement orderElement, WorkReport workReport); + + Boolean isFinished(OrderElement orderElement); + List findByOrderElementAndWorkReports( OrderElement orderElement, List workReports); diff --git a/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/WorkReportLineDAO.java b/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/WorkReportLineDAO.java index be6162603..f5c9770bc 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/WorkReportLineDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/WorkReportLineDAO.java @@ -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 return criteria.list(); } + @Override + public Pair 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 findByOrderElementNotInWorkReportAnotherTransaction( + OrderElement orderElement, WorkReport workReport) { + return findByOrderElementNotInWorkReport(orderElement, workReport); + } + + @SuppressWarnings("unchecked") + private List 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) 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 findByOrderElementAndWorkReports( diff --git a/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReport.java b/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReport.java index b1d501fba..caca48337 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReport.java +++ b/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReport.java @@ -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 finishedOrderElements = new HashSet(); + + for (WorkReportLine line : workReportLines) { + if (line.isFinished()) { + if (Util.contains(finishedOrderElements, line.getOrderElement())) { + return false; + } + finishedOrderElements.add(line.getOrderElement()); + } + } + + return true; + } + } diff --git a/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportLine.java b/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportLine.java index 15e07a6d6..c291fecc6 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportLine.java +++ b/libreplan-business/src/main/java/org/libreplan/business/workreports/entities/WorkReportLine.java @@ -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 lines = Registry.getWorkReportLineDAO() + .findByOrderElementNotInWorkReportAnotherTransaction( + orderElement, workReport); + for (WorkReportLine line : lines) { + if (line.isFinished()) { + return false; + } + } + + return true; + } + } diff --git a/libreplan-business/src/main/resources/db.changelog-1.3.xml b/libreplan-business/src/main/resources/db.changelog-1.3.xml index 23d1feb29..a538a6dec 100644 --- a/libreplan-business/src/main/resources/db.changelog-1.3.xml +++ b/libreplan-business/src/main/resources/db.changelog-1.3.xml @@ -73,6 +73,88 @@ + + + Add columns first_timesheet_date and last_timesheet_date to + sum_charged_effort table + + + + + + + + + + + + Add new column read_only with default value FALSE to advance_type + table. + + + + + + + + + + + Add new column finished with default value FALSE to + work_report_line table. + + + + + + + + + + + Add new column finished_timesheets with default value FALSE to + sum_charged_effort table. + + + + + + + + + + + Add new column updated_from_timesheets with default value FALSE to + task_element table. + + + + + + + + Updating status values in order_table diff --git a/libreplan-business/src/main/resources/org/libreplan/business/advance/entities/Advance.hbm.xml b/libreplan-business/src/main/resources/org/libreplan/business/advance/entities/Advance.hbm.xml index 5476067f9..acfe6608e 100644 --- a/libreplan-business/src/main/resources/org/libreplan/business/advance/entities/Advance.hbm.xml +++ b/libreplan-business/src/main/resources/org/libreplan/business/advance/entities/Advance.hbm.xml @@ -23,6 +23,7 @@ + diff --git a/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml b/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml index 2a7096a5d..e6d2a28d1 100644 --- a/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml +++ b/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml @@ -272,6 +272,14 @@ + + + + + diff --git a/libreplan-business/src/main/resources/org/libreplan/business/planner/entities/Tasks.hbm.xml b/libreplan-business/src/main/resources/org/libreplan/business/planner/entities/Tasks.hbm.xml index 43357189d..a370b9024 100644 --- a/libreplan-business/src/main/resources/org/libreplan/business/planner/entities/Tasks.hbm.xml +++ b/libreplan-business/src/main/resources/org/libreplan/business/planner/entities/Tasks.hbm.xml @@ -52,6 +52,9 @@ + + diff --git a/libreplan-business/src/main/resources/org/libreplan/business/workreports/entities/WorkReports.hbm.xml b/libreplan-business/src/main/resources/org/libreplan/business/workreports/entities/WorkReports.hbm.xml index 1439f5bac..6ff0c4a85 100644 --- a/libreplan-business/src/main/resources/org/libreplan/business/workreports/entities/WorkReports.hbm.xml +++ b/libreplan-business/src/main/resources/org/libreplan/business/workreports/entities/WorkReports.hbm.xml @@ -162,6 +162,8 @@ + + diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/Util.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/Util.java index 30f263d78..3e33a3591 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/common/Util.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/Util.java @@ -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 bind(final C checkBox, + public static Checkbox bind(final Checkbox checkBox, final Getter getter, final Setter setter) { checkBox.setChecked(getter.get()); checkBox.addEventListener(Events.ON_CHECK, new EventListener() { diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/components/CapacityPicker.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/components/CapacityPicker.java index 1a710f67c..399b2b5d4 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/common/components/CapacityPicker.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/components/CapacityPicker.java @@ -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 diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/IManageOrderElementAdvancesModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/IManageOrderElementAdvancesModel.java index 4cd1b9b83..80333810e 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/IManageOrderElementAdvancesModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/IManageOrderElementAdvancesModel.java @@ -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); + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/ManageOrderElementAdvancesController.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/ManageOrderElementAdvancesController.java index 3154f5502..2ff8aa908 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/ManageOrderElementAdvancesController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/ManageOrderElementAdvancesController.java @@ -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 diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/ManageOrderElementAdvancesModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/ManageOrderElementAdvancesModel.java index 6f177e6ec..553864c29 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/ManageOrderElementAdvancesModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/ManageOrderElementAdvancesModel.java @@ -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( diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/TaskElementAdapter.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/TaskElementAdapter.java index c97f4cbfa..4c7ff4345 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/TaskElementAdapter.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/TaskElementAdapter.java @@ -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 diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/adaptplanning/AdaptPlanningCommand.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/adaptplanning/AdaptPlanningCommand.java new file mode 100644 index 000000000..902bf4e19 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/adaptplanning/AdaptPlanningCommand.java @@ -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 . + */ +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 + */ +@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 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 context) { + List 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 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; + } + +} \ No newline at end of file diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/adaptplanning/IAdaptPlanningCommand.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/adaptplanning/IAdaptPlanningCommand.java new file mode 100644 index 000000000..3826ad0d8 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/adaptplanning/IAdaptPlanningCommand.java @@ -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 . + */ +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 + */ +public interface IAdaptPlanningCommand extends ICommand { + + public void setState(PlanningState planningState); + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AdvancedAllocationController.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AdvancedAllocationController.java index 7b9bf16a1..d4d69ff62 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AdvancedAllocationController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AdvancedAllocationController.java @@ -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 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, diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AllocationConfiguration.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AllocationConfiguration.java index 420554f13..3c13cf7a9 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AllocationConfiguration.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AllocationConfiguration.java @@ -107,7 +107,8 @@ public class AllocationConfiguration extends HtmlMacroComponent { .getCalculatedValue())) { radio.setChecked(true); } - radio.setDisabled(formBinder.isAnyManual()); + radio.setDisabled(formBinder.isAnyManual() + || formBinder.isTaskUpdatedFromTimesheets()); } } }; diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AllocationRowsHandler.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AllocationRowsHandler.java index b861f7a0d..5c67b320d 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AllocationRowsHandler.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/AllocationRowsHandler.java @@ -423,4 +423,9 @@ public class AllocationRowsHandler { private ArrayList copyOfCurrentRowsToAvoidConcurrentModification() { return new ArrayList(currentRows); } + + public boolean isTaskUpdatedFromTimesheets() { + return task.isUpdatedFromTimesheets(); + } + } \ No newline at end of file diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/FormBinder.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/FormBinder.java index ca0610ffb..cf9e0692d 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/FormBinder.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/FormBinder.java @@ -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 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(); + } + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/ResourceAllocationController.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/ResourceAllocationController.java index e3045f3b6..0b2509bc2 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/ResourceAllocationController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/allocation/ResourceAllocationController.java @@ -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(); } } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/OrderPlanningModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/OrderPlanningModel.java index 0830dfb63..8f76bce12 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/OrderPlanningModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/OrderPlanningModel.java @@ -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 separator = new NullSeparatorCommandOnTask(); @@ -1055,6 +1060,11 @@ public class OrderPlanningModel implements IOrderPlanningModel { return reassignCommand; } + private ICommand buildAdaptPlanningCommand() { + adaptPlanningCommand.setState(planningState); + return adaptPlanningCommand; + } + private ICommand buildCancelEditionCommand() { return new ICommand() { @@ -1097,6 +1107,11 @@ public class OrderPlanningModel implements IOrderPlanningModel { return false; } + @Override + public boolean isPlannerCommand() { + return false; + } + }; } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java index 8a065b2ef..438b0455f 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java @@ -1038,6 +1038,11 @@ public class SaveCommandBuilder { return disabled; } + @Override + public boolean isPlannerCommand() { + return false; + } + } private static final class LabelCreatorForInvalidValues implements diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/reassign/ReassignCommand.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/reassign/ReassignCommand.java index 7456963f4..5d8671413 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/reassign/ReassignCommand.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/reassign/ReassignCommand.java @@ -375,4 +375,9 @@ public class ReassignCommand implements IReassignCommand { return false; } + @Override + public boolean isPlannerCommand() { + return true; + } + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/reassign/ReassignConfiguration.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/reassign/ReassignConfiguration.java index f8e77285c..e17a51a3d 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/reassign/ReassignConfiguration.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/reassign/ReassignConfiguration.java @@ -61,6 +61,9 @@ public class ReassignConfiguration { } private boolean isChoosenForReassignation(Task each) { + if (each.isUpdatedFromTimesheets()) { + return false; + } return type == Type.ALL || isAfterDate(each); } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/taskedition/TaskPropertiesController.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/taskedition/TaskPropertiesController.java index ad475fb10..ea07b713e 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/taskedition/TaskPropertiesController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/taskedition/TaskPropertiesController.java @@ -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(); diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeController.java b/libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeController.java index c4aaa21c7..7f0a1e87f 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeController.java @@ -116,6 +116,7 @@ public abstract class TreeController> extends viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree); getModel().indent(element); filterByPredicateIfAny(); + updateControlButtons(); } public TreeModel getTreeModel() { @@ -140,6 +141,7 @@ public abstract class TreeController> extends viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree); getModel().unindent(element); filterByPredicateIfAny(); + updateControlButtons(); } public void up() { @@ -153,6 +155,7 @@ public abstract class TreeController> extends viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree); getModel().up(element); filterByPredicateIfAny(); + updateControlButtons(); } public void down() { @@ -165,6 +168,7 @@ public abstract class TreeController> extends viewStateSnapshot = TreeViewStateSnapshot.takeSnapshot(tree); getModel().down(element); filterByPredicateIfAny(); + updateControlButtons(); } public T getSelectedNode() { @@ -184,8 +188,15 @@ public abstract class TreeController> 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> extends private List 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> 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> 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> 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> 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> extends } }); schedulingStateToggler.afterCompose(); - cell.setDraggable("true"); + } protected abstract SchedulingState getSchedulingStateFrom( @@ -1105,92 +1140,6 @@ public abstract class TreeController> 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> extends public void doTry() { } + } public void setColumns(List columns) { @@ -1286,17 +1236,40 @@ public abstract class TreeController> 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(); diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/users/dashboard/PersonalTimesheetModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/users/dashboard/PersonalTimesheetModel.java index df067c439..16c2a0dcf 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/users/dashboard/PersonalTimesheetModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/users/dashboard/PersonalTimesheetModel.java @@ -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 deletedWorkReportLinesSet = removeWorkReportLinesWithEffortZero(); + + Set 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 removeWorkReportLinesWithEffortZero() { + Set toRemove = new HashSet(); + 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>(); diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/IWorkReportModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/IWorkReportModel.java index 789507fd7..5c54666ea 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/IWorkReportModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/IWorkReportModel.java @@ -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); + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportCRUDController.java b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportCRUDController.java index f9311bde7..381161b04 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportCRUDController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportCRUDController.java @@ -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() { + @Override + public Boolean get() { + return line.isFinished(); + } + }, new Setter() { + @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); } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportModel.java index f05831956..5cf147a77 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportModel.java @@ -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 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 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 lines = workReportLineDAO + .findByOrderElementNotInWorkReportAnotherTransaction( + orderElement, workReport); + for (WorkReportLine line : lines) { + if (line.isFinished()) { + return true; + } + } + return false; + } + } diff --git a/libreplan-webapp/src/main/java/org/libreplan/ws/common/impl/GenericRESTService.java b/libreplan-webapp/src/main/java/org/libreplan/ws/common/impl/GenericRESTService.java index 278bad001..2e324e383 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/ws/common/impl/GenericRESTService.java +++ b/libreplan-webapp/src/main/java/org/libreplan/ws/common/impl/GenericRESTService.java @@ -143,6 +143,7 @@ public abstract class GenericRESTService implements IWorkReportService { + private Set 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 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 orderElements = sumChargedEffortDAO + .getOrderElementsToRecalculateTimsheetDates(null, + Collections.singleton(workReportLine)); sumChargedEffortDAO .updateRelatedSumChargedEffortWithDeletedWorkReportLineSet(new HashSet( Arrays.asList(workReportLine))); workReportLineDAO.remove(workReportLine.getId()); + sumChargedEffortDAO.recalculateTimesheetData(orderElements); return Response.ok().build(); } catch (InstanceNotFoundException e) { return Response.status(Status.NOT_FOUND).build(); diff --git a/libreplan-webapp/src/main/webapp/common/img/ico_adapt_planning.png b/libreplan-webapp/src/main/webapp/common/img/ico_adapt_planning.png new file mode 100644 index 000000000..6b6aa43c9 Binary files /dev/null and b/libreplan-webapp/src/main/webapp/common/img/ico_adapt_planning.png differ diff --git a/libreplan-webapp/src/main/webapp/orders/components/_orderElementTree.zul b/libreplan-webapp/src/main/webapp/orders/components/_orderElementTree.zul index ca0ca9c8d..17c919d3c 100644 --- a/libreplan-webapp/src/main/webapp/orders/components/_orderElementTree.zul +++ b/libreplan-webapp/src/main/webapp/orders/components/_orderElementTree.zul @@ -82,7 +82,7 @@ diff --git a/libreplan-webapp/src/main/webapp/planner/css/ganttzk.css b/libreplan-webapp/src/main/webapp/planner/css/ganttzk.css index ff27669f8..a9aa526b4 100644 --- a/libreplan-webapp/src/main/webapp/planner/css/ganttzk.css +++ b/libreplan-webapp/src/main/webapp/planner/css/ganttzk.css @@ -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; diff --git a/libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul b/libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul index 302c4ac97..271e6b0ff 100644 --- a/libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul +++ b/libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul @@ -64,7 +64,7 @@