/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.libreplan.web.planner.allocation.stretches; import static org.libreplan.web.I18nHelper._; import java.math.BigDecimal; import java.text.DateFormat; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import org.joda.time.LocalDate; import org.libreplan.business.calendars.entities.BaseCalendar; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.planner.daos.IAssignmentFunctionDAO; import org.libreplan.business.planner.daos.ITaskElementDAO; import org.libreplan.business.planner.daos.ITaskSourceDAO; import org.libreplan.business.planner.entities.AssignmentFunction; import org.libreplan.business.planner.entities.ResourceAllocation; import org.libreplan.business.planner.entities.Stretch; import org.libreplan.business.planner.entities.StretchesFunction; import org.libreplan.business.planner.entities.StretchesFunctionTypeEnum; import org.libreplan.business.planner.entities.Task; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.zkoss.util.Locales; /** * Model for UI operations related to {@link StretchesFunction} configuration. * * @author Manuel Rego Casasnovas <mrego@igalia.com> * @author Diego Pino García <dpino@igalia.com> */ @Service @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class StretchesFunctionModel implements IStretchesFunctionModel { /** * Conversation state */ private StretchesFunction stretchesFunction; private Task task; private Date taskEndDate; private StretchesFunction originalStretchesFunction; @Autowired private ITaskElementDAO taskElementDAO; @Autowired private IAssignmentFunctionDAO assignmentFunctionDAO; @Autowired private ITaskSourceDAO taskSourceDAO; private ResourceAllocation<?> resourceAllocation; @Override @Transactional(readOnly = true) public void init( StretchesFunction stretchesFunction, ResourceAllocation<?> resourceAllocation, StretchesFunctionTypeEnum type) { if (stretchesFunction != null) { assignmentFunctionDAO.reattach(stretchesFunction); // Initialize resourceAllocation and task this.resourceAllocation = resourceAllocation; this.task = resourceAllocation.getTask(); forceLoadData(); this.taskEndDate = task.getEndDate(); // Initialize stretchesFunction stretchesFunction.setResourceAllocation(resourceAllocation); this.originalStretchesFunction = stretchesFunction; this.stretchesFunction = stretchesFunction.copy(); this.stretchesFunction.changeTypeTo(type); addConsolidatedStretchIfAny(); } } private void addConsolidatedStretchIfAny() { Stretch consolidated = consolidatedStretchFor(resourceAllocation); if (consolidated != null) { stretchesFunction.setConsolidatedStretch(consolidated); } } private Stretch consolidatedStretchFor(ResourceAllocation<?> resourceAllocation) { return Stretch.buildFromConsolidatedProgress(resourceAllocation); } private void forceLoadData() { taskSourceDAO.reattach(task.getTaskSource()); taskElementDAO.reattach(task); task.getCalendar(); this.resourceAllocation.getAssignedHours(); } @Override public List<Stretch> getStretchesDefinedByUser() { if (stretchesFunction == null) { return Collections.emptyList(); } return stretchesFunction.getStretches(); } @Override public List<Stretch> getAllStretches() { if (stretchesFunction == null) { return Collections.emptyList(); } return Collections.unmodifiableList(allStretches()); } /** * Returns an empty stretch plus the stretches from stretchesFunction and the consolidated stretch if any. * * @return {@link List<Stretch>} */ private List<Stretch> allStretches() { return stretchesFunction.getStretchesPlusConsolidated(); } @Override public List<Stretch> getStretchesPlusConsolidated() { if (stretchesFunction == null) { return Collections.emptyList(); } return Collections.unmodifiableList(stretchesFunction .getStretchesPlusConsolidated()); } @Override public void confirm() throws ValidationException { if (stretchesFunction != null) { if (!stretchesFunction.isNoEmptyConstraint()) { throw new ValidationException( _("At least one stretch is needed")); } if (!stretchesFunction.isStretchesOrderConstraint()) { throw new ValidationException( _("Some stretch has higher or equal values than the " + "previous stretch")); } if (!stretchesFunction.isOneHundredPercentConstraint()) { throw new ValidationException( _("Last stretch should have 100% for length and amount of work")); } if (stretchesFunction.isInterpolated()) { if (!stretchesFunction.checkHasAtLeastTwoStretches()) { throw new ValidationException( _("There must be at least 2 stretches for doing interpolation")); } } if (originalStretchesFunction != null) { originalStretchesFunction.resetToStretchesFrom(stretchesFunction); originalStretchesFunction.changeTypeTo(stretchesFunction.getDesiredType()); stretchesFunction = originalStretchesFunction; } } } public static boolean areValidForInterpolation(List<Stretch> stretches) { return atLeastThreeStretches(stretches) && areStretchesSortedAndWithIncrements(stretches); } private static boolean atLeastThreeStretches(List<Stretch> stretches) { return stretches.size() >= 3; } private static boolean areStretchesSortedAndWithIncrements( List<Stretch> stretches) { if (stretches.isEmpty()) { return false; } Iterator<Stretch> iterator = stretches.iterator(); Stretch previous = iterator.next(); while (iterator.hasNext()) { Stretch current = iterator.next(); if (current.getLengthPercentage().compareTo( previous.getLengthPercentage()) <= 0) { return false; } if (current.getAmountWorkPercentage().compareTo( previous.getAmountWorkPercentage()) <= 0) { return false; } previous = current; } return true; } @Override public void cancel() { stretchesFunction = originalStretchesFunction; } @Override public void addStretch() { if (stretchesFunction != null) { stretchesFunction.addStretch(newStretch()); } } private Stretch newStretch() { LocalDate startDate = getTaskStartDate(); BigDecimal amountWorkPercent = BigDecimal.ZERO; Stretch consolidatedStretch = stretchesFunction .getConsolidatedStretch(); if (consolidatedStretch != null) { startDate = consolidatedStretch.getDateIn(resourceAllocation) .plusDays(1); amountWorkPercent = consolidatedStretch.getAmountWorkPercentage().add(BigDecimal.ONE.divide(BigDecimal.valueOf(100))); } return Stretch.create(startDate, resourceAllocation, amountWorkPercent); } @Override public LocalDate getTaskStartDate() { if (task == null) { return null; } return task.getStartAsLocalDate(); } @Override public void removeStretch(Stretch stretch) { if (stretchesFunction != null) { stretchesFunction.removeStretch(stretch); } } @Override public AssignmentFunction getStretchesFunction() { return stretchesFunction; } @Override public Date getStretchDate(Stretch stretch) { return stretch.getDateIn(resourceAllocation).toDateTimeAtStartOfDay() .toDate(); } @Override public void setStretchDate(Stretch stretch, Date date) throws IllegalArgumentException { if (date.compareTo(task.getStartDate()) < 0) { throw new IllegalArgumentException( _("Stretch date must not be before task start date: " + sameFormatAsDefaultZK(task.getStartDate()))); } if (date.compareTo(taskEndDate) > 0) { throw new IllegalArgumentException( _("Stretch date must be earlier than End date: " + sameFormatAsDefaultZK(taskEndDate))); } stretch.setDateIn(resourceAllocation, new LocalDate(date)); } private String sameFormatAsDefaultZK(Date date) { DateFormat zkDefaultDateFormatter = DateFormat.getDateInstance( DateFormat.DEFAULT, Locales.getCurrent()); DateFormat formatter = zkDefaultDateFormatter; return formatter.format(date); } @Override public void setStretchLengthPercentage(Stretch stretch, BigDecimal lengthPercentage) throws IllegalArgumentException { stretch.setLengthPercentage(lengthPercentage); } @Override public Integer getAllocationHours() { if (task == null) { return null; } return resourceAllocation.getAssignedHours(); } @Override public BaseCalendar getTaskCalendar() { if (task == null) { return null; } return task.getCalendar(); } @Override public ResourceAllocation<?> getResourceAllocation() { return resourceAllocation; } }