org.libreplan.web.planner.allocation.FormBinder.java Source code

Java tutorial

Introduction

Here is the source code for org.libreplan.web.planner.allocation.FormBinder.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2009-2010 Fundacin para o Fomento da Calidade Industrial e
 *                         Desenvolvemento Tecnolxico 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;

import static org.libreplan.business.workingday.EffortDuration.hours;
import static org.libreplan.business.workingday.EffortDuration.zero;
import static org.libreplan.web.I18nHelper._;
import static org.libreplan.web.planner.allocation.AllocationRow.assignEfforts;
import static org.libreplan.web.planner.allocation.AllocationRow.sumAllEffortFromInputs;
import static org.libreplan.web.planner.allocation.AllocationRow.sumAllOriginalEffort;
import static org.libreplan.web.planner.allocation.AllocationRow.sumAllTotalEffort;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.libreplan.business.common.Flagged;
import org.libreplan.business.common.ProportionalDistributor;
import org.libreplan.business.planner.entities.AggregateOfResourceAllocations;
import org.libreplan.business.planner.entities.CalculatedValue;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.ResourceEnum;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workingday.ResourcesPerDay;
import org.libreplan.web.common.EffortDurationBox;
import org.libreplan.web.common.IMessagesForUser;
import org.libreplan.web.common.Level;
import org.libreplan.web.common.Util;
import org.libreplan.web.common.components.NewAllocationSelectorCombo;
import org.libreplan.web.common.components.ResourceAllocationBehaviour;
import org.libreplan.web.planner.allocation.AllocationRowsHandler.Warnings;
import org.libreplan.web.planner.allocation.IResourceAllocationModel.IResourceAllocationContext;
import org.libreplan.web.planner.taskedition.TaskPropertiesController;
import org.zkoss.util.Locales;
import org.zkoss.zk.au.out.AuWrongValue;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Button;
import org.zkoss.zul.Constraint;
import org.zkoss.zul.Decimalbox;
import org.zkoss.zul.Grid;
import org.zkoss.zul.Intbox;
import org.zkoss.zul.Label;
import org.zkoss.zul.SimpleConstraint;
import org.zkoss.zul.Tab;
import org.zkoss.zul.impl.InputElement;

public class FormBinder {

    private EffortDurationBox effortInput;

    private Label allOriginalEffort;

    private Label allTotalEffort;

    private Label allConsolidatedEffort;

    private Label allTotalResourcesPerDay;

    private Label allConsolidatedResourcesPerDay;

    private final AllocationRowsHandler allocationRowsHandler;

    private AggregateOfResourceAllocations aggregate;

    private AllocationResult lastAllocation;

    private Button applyButton;

    private EventListener onChangeEnableApply = new EventListener() {

        @Override
        public void onEvent(Event event) {
            Component target = event.getTarget();
            if (target instanceof InputElement) {
                InputElement inputElement = (InputElement) target;
                if (inputElement.isDisabled()) {
                    return;
                }
            }
            Clients.clearWrongValue(allocationsGrid);
            applyButton.setDisabled(false);
        }
    };

    private ResourceAllocationBehaviour behaviour;

    private Grid allocationsGrid;

    private EventListener applyButtonListener;

    private List<InputElement> inputsAssociatedWithOnChangeEnableApply = new ArrayList<>();

    private IMessagesForUser messagesForUser;

    private final IResourceAllocationModel resourceAllocationModel;

    private List<AllocationRow> rows = Collections.emptyList();

    private WorkableDaysAndDatesBinder workableDaysAndDatesBinder;

    private Button btnRecommendedAllocation;

    private ProportionalDistributor hoursDistributorForRecommendedAllocation;

    private ResourcesPerDay.ResourcesPerDayDistributor resourcesPerDayDistributorForRecommendedAllocation;

    private EventListener hoursRowInputChange = new EventListener() {

        @Override
        public void onEvent(Event event) {
            if (effortInput.isDisabled()) {
                effortInput.setValue(sumAllEffortFromInputs(rows));
            }
        }
    };

    private EventListener resourcesPerDayRowInputChange = new EventListener() {

        @Override
        public void onEvent(Event event) {
            if (allResourcesPerDay.isDisabled()) {
                sumResourcesPerDayFromRowsAndAssignToAllResourcesPerDay();
            }
        }
    };

    private EventListener allHoursInputChange = new EventListener() {

        @Override
        public void onEvent(Event event) {
            if (!effortInput.isDisabled()) {
                distributeHoursFromTotalToRows();
            }
        }
    };

    private EventListener allResourcesPerDayChange = new EventListener() {

        @Override
        public void onEvent(Event event) {
            if (!allResourcesPerDay.isDisabled()) {
                distributeResourcesPerDayToRows();
            }
        }
    };

    private boolean recommendedAllocation = false;

    private NewAllocationSelectorCombo newAllocationSelectorCombo;

    private Tab workerSearchTab;

    private Decimalbox allResourcesPerDay;

    private Button advancedSearchButton;

    public FormBinder(Scenario currentScenario, AllocationRowsHandler allocationRowsHandler,
            IResourceAllocationModel resourceAllocationModel) {

        this.allocationRowsHandler = allocationRowsHandler;
        this.resourceAllocationModel = resourceAllocationModel;
        this.lastAllocation = this.allocationRowsHandler.getInitialAllocation(currentScenario);
        this.aggregate = this.lastAllocation.getAggregate();
    }

    void setAssignedEffortComponent(EffortDurationBox assignedEffortComponent) {
        this.effortInput = assignedEffortComponent;
        this.effortInput.setConstraint(positiveValueRequired());
        allHoursInputComponentDisabilityRule();
        loadValueForEffortInput();
        onChangeEnableApply(assignedEffortComponent);
    }

    private void loadValueForEffortInput() {
        this.effortInput.setValue(aggregate.isEmpty() ? hours(allocationRowsHandler.getTask().getWorkHours())
                : aggregate.getTotalEffort());
    }

    private void allHoursInputComponentDisabilityRule() {
        CalculatedValue c = allocationRowsHandler.getCalculatedValue();
        boolean disabled = rows.isEmpty() || (CalculatedValue.NUMBER_OF_HOURS == c)
                || (c == CalculatedValue.RESOURCES_PER_DAY && !recommendedAllocation) || isAnyManual()
                || isTaskUpdatedFromTimesheets();

        this.effortInput.setDisabled(disabled);
    }

    public AllocationResult getLastAllocation() {
        return lastAllocation;
    }

    void setCalculatedValue(CalculatedValue newCalculatedValue) {
        CalculatedValue previousCalculatedValue = allocationRowsHandler.getCalculatedValue();
        if (newCalculatedValue == previousCalculatedValue) {
            return;
        }
        allocationRowsHandler.setCalculatedValue(newCalculatedValue);
        applyDisabledRules();
        sumResourcesPerDayFromRowsAndAssignToAllResourcesPerDay();
        workableDaysAndDatesBinder.switchFromTo(previousCalculatedValue, newCalculatedValue);
        applyButton.setDisabled(false);
    }

    private void applyDisabledRules() {
        allHoursInputComponentDisabilityRule();
        workableDaysAndDatesBinder.applyDisabledRules();
        allResourcesPerDayVisibilityRule();
        applyDisabledRulesOnRows();
        this.btnRecommendedAllocation
                .setDisabled(recommendedAllocation || isAnyManual() || isTaskUpdatedFromTimesheets());
    }

    private void applyDisabledRulesOnRows() {
        for (AllocationRow each : rows) {
            each.applyDisabledRules(getCalculatedValue(), recommendedAllocation,
                    isAnyManual() || isTaskUpdatedFromTimesheets());
        }
    }

    private void bindTotalHoursToHoursInputs() {
        for (AllocationRow each : rows) {
            each.addListenerForHoursInputChange(hoursRowInputChange);
        }
        effortInput.setValue(sumAllEffortFromInputs(this.rows));
    }

    CalculatedValue getCalculatedValue() {
        return allocationRowsHandler.getCalculatedValue();
    }

    private void onChangeEnableApply(InputElement inputElement) {
        Util.ensureUniqueListener(inputElement, Events.ON_CHANGE, onChangeEnableApply);
    }

    void setWorkableDays(Intbox duration, final TaskPropertiesController taskPropertiesController,
            final Label labelTaskStart, final Label labelTaskEnd) {

        this.workableDaysAndDatesBinder = new WorkableDaysAndDatesBinder(duration, labelTaskStart, labelTaskEnd,
                taskPropertiesController);
    }

    private class WorkableDaysAndDatesBinder {

        private Intbox taskWorkableDays;

        private Label labelTaskStart;

        private Label labelTaskEnd;

        private final TaskPropertiesController taskPropertiesController;

        WorkableDaysAndDatesBinder(final Intbox taskWorkableDays, final Label labelTaskStart,
                final Label labelTaskEnd, final TaskPropertiesController taskPropertiesController) {

            this.taskWorkableDays = taskWorkableDays;
            this.labelTaskStart = labelTaskStart;
            this.labelTaskEnd = labelTaskEnd;
            this.taskPropertiesController = taskPropertiesController;
            initializeDateAndDurationFieldsFromTaskOriginalValues();
            final LocalDate firstPossibleDay = getTask().getFirstDayNotConsolidated().nextDayAtStart()
                    .asExclusiveEnd();
            Util.ensureUniqueListeners(taskWorkableDays, Events.ON_CHANGE, new EventListener() {

                @Override
                public void onEvent(Event event) {
                    Task task = getTask();
                    Integer workableDays = taskWorkableDays.getValue();
                    if (allocationRowsHandler.isForwardsAllocation()) {

                        IntraDayDate newEnd = ensureItIsAfterConsolidation(
                                task.calculateEndGivenWorkableDays(workableDays));

                        updateWorkableDaysIfNecessary(workableDays, getTask().getIntraDayStartDate(), newEnd);
                        taskPropertiesController.updateTaskEndDate(newEnd.getDate());
                        showValueOfDateOn(labelTaskEnd, newEnd.getDate());
                    } else {

                        IntraDayDate newStart = ensureItIsAfterConsolidation(
                                task.calculateStartGivenWorkableDays(workableDays));

                        updateWorkableDaysIfNecessary(workableDays, newStart, task.getIntraDayEndDate());
                        taskPropertiesController.updateTaskStartDate(newStart.getDate());
                        showValueOfDateOn(labelTaskStart, newStart.getDate());
                    }
                }

                private void updateWorkableDaysIfNecessary(int specifiedWorkableDays, IntraDayDate allocationStart,
                        IntraDayDate allocationEnd) {

                    Integer effectiveWorkableDays = getTask().getWorkableDaysFrom(allocationStart.getDate(),
                            allocationEnd.asExclusiveEnd());

                    if (effectiveWorkableDays < specifiedWorkableDays) {

                        Clients.response(new AuWrongValue(taskWorkableDays, _(
                                "The original workable days value {0} cannot be modified as it has consolidations",
                                specifiedWorkableDays)));

                        taskWorkableDays.setValue(effectiveWorkableDays);
                    }
                }

                @SuppressWarnings("unchecked")
                private IntraDayDate ensureItIsAfterConsolidation(IntraDayDate newDate) {
                    if (getTask().hasConsolidations()) {
                        return Collections.max(Arrays.asList(newDate, IntraDayDate.startOfDay(firstPossibleDay)));
                    }

                    return newDate;
                }

            }, onChangeEnableApply);
            applyDisabledRules();
        }

        void applyDisabledRules() {
            this.taskWorkableDays.setDisabled(allocationRowsHandler.getCalculatedValue() == CalculatedValue.END_DATE
                    || isAnyManual() || isTaskUpdatedFromTimesheets());
        }

        private void initializeDateAndDurationFieldsFromTaskOriginalValues() {
            Task task = getTask();
            showValueOfDateOn(labelTaskStart, task.getStartAsLocalDate());
            showValueOfDateOn(labelTaskEnd, task.getEndAsLocalDate());

            taskWorkableDays.setConstraint(positiveValueRequired());
            taskWorkableDays.setValue(task.getWorkableDays());
        }

        Integer getValue() {
            return taskWorkableDays.getValue();
        }

        private Integer lastSpecifiedWorkableDays = null;

        void switchFromTo(CalculatedValue previousCalculatedValue, CalculatedValue newCalculatedValue) {
            if (newCalculatedValue == CalculatedValue.END_DATE) {
                clearDateAndDurationFields();
            } else if (previousCalculatedValue == CalculatedValue.END_DATE && taskWorkableDays.getValue() == null) {
                initializeDateAndDurationFieldsFromLastValues();
            }
        }

        private void clearDateAndDurationFields() {
            (allocationRowsHandler.isForwardsAllocation() ? labelTaskEnd : labelTaskStart).setValue("");
            taskWorkableDays.setConstraint((Constraint) null);
            lastSpecifiedWorkableDays = taskWorkableDays.getValue();
            taskWorkableDays.setValue(null);
        }

        private void initializeDateAndDurationFieldsFromLastValues() {
            if (lastSpecifiedWorkableDays == null) {
                initializeDateAndDurationFieldsFromTaskOriginalValues();
            } else {
                Task task = getTask();
                taskWorkableDays.setConstraint(positiveValueRequired());
                taskWorkableDays.setValue(lastSpecifiedWorkableDays);
                showValueOfDateOn(labelTaskEnd,
                        task.calculateEndGivenWorkableDays(lastSpecifiedWorkableDays).getDate());
                lastSpecifiedWorkableDays = null;
            }
        }

        void afterApplicationReloadValues() {
            if (getCalculatedValue() != CalculatedValue.END_DATE || aggregate.isEmpty()) {
                return;
            }

            LocalDate start = aggregate.getStart().getDate();
            LocalDate end = aggregate.getEnd().asExclusiveEnd();
            taskPropertiesController.updateTaskStartDate(start);
            taskPropertiesController.updateTaskEndDate(end);
            showValueOfDateOn(labelTaskStart, start);
            showValueOfDateOn(labelTaskEnd, end);
            taskWorkableDays.setValue(getTask().getWorkableDaysFrom(start, end));
        }

        private void showValueOfDateOn(final Label label, LocalDate date) {
            DateTimeFormatter formatter = DateTimeFormat.forStyle("S-").withLocale(Locales.getCurrent());
            label.setValue(formatter.print(date));
        }

    }

    private static SimpleConstraint positiveValueRequired() {
        return new SimpleConstraint(SimpleConstraint.NO_EMPTY | SimpleConstraint.NO_NEGATIVE);
    }

    IntraDayDate getAllocationEnd() {
        return getTask().calculateEndGivenWorkableDays(workableDaysAndDatesBinder.getValue());
    }

    IntraDayDate getAllocationStart() {
        return getTask().calculateStartGivenWorkableDays(workableDaysAndDatesBinder.getValue());
    }

    private Task getTask() {
        return allocationRowsHandler.getTask();
    }

    void setAllResourcesPerDay(Decimalbox allResourcesPerDay) {
        this.allResourcesPerDay = allResourcesPerDay;
        this.allResourcesPerDay.setConstraint(positiveValueRequired());
        allResourcesPerDayVisibilityRule();
        onChangeEnableApply(allResourcesPerDay);
    }

    private void allResourcesPerDayVisibilityRule() {
        CalculatedValue c = allocationRowsHandler.getCalculatedValue();

        this.allResourcesPerDay.setDisabled(rows.isEmpty() || c == CalculatedValue.RESOURCES_PER_DAY
                || !recommendedAllocation || isAnyManual() || isTaskUpdatedFromTimesheets());

        this.allResourcesPerDay.setConstraint(constraintForAllResourcesPerDay());
    }

    private Constraint constraintForAllResourcesPerDay() {
        if (allResourcesPerDay.isDisabled()) {
            return null;
        }

        return AllocationRow.CONSTRAINT_FOR_RESOURCES_PER_DAY;
    }

    List<AllocationRow> getCurrentRows() {
        List<AllocationRow> result = addListeners(allocationRowsHandler.getCurrentRows());
        rows = result;
        setupRows();

        return result;
    }

    private void setupRows() {
        applyDisabledRulesOnRows();
        bindTotalHoursToHoursInputs();
        allHoursInputComponentDisabilityRule();
        bindAllResourcesPerDayToRows();
        allResourcesPerDayVisibilityRule();
        loadAggregatedCalculations();
    }

    private void bindAllResourcesPerDayToRows() {
        sumResourcesPerDayFromRowsAndAssignToAllResourcesPerDay();
        for (AllocationRow each : rows) {
            each.addListenerForResourcesPerDayInputChange(resourcesPerDayRowInputChange);
        }
    }

    private List<AllocationRow> addListeners(List<AllocationRow> list) {
        for (AllocationRow each : list) {
            each.addListenerForInputChange(onChangeEnableApply);
        }

        return list;
    }

    private void doApply() {
        AllocationResult allocationResult = resourceAllocationModel
                .onAllocationContext(new IResourceAllocationContext<AllocationResult>() {
                    @Override
                    public AllocationResult doInsideTransaction() {
                        return allocationRowsHandler.doAllocation().getValue();
                    }
                });

        allocationProduced(allocationResult);

        TaskPropertiesController.allocationResult = allocationResult;
    }

    /**
     * @return <code>true</code> if and only if operation completed and must
     *         exit the edition form
     */
    public boolean accept() {
        if (isTaskUpdatedFromTimesheets()) {
            return true;
        }

        Flagged<AllocationResult, Warnings> result = resourceAllocationModel.accept();

        // result can be null when editing milestones
        if (result != null && result.isFlagged()) {
            allocationProduced(result.getValue());
        }

        return result == null || !result.isFlagged();
    }

    private void allocationProduced(AllocationResult allocationResult) {
        lastAllocation = allocationResult;
        aggregate = lastAllocation.getAggregate();

        loadResourcesPerDay();
        loadEffortValues();
        loadDerivedAllocations();
        loadSclassRowSatisfied();
        loadAssignmentFunctionNames();

        workableDaysAndDatesBinder.afterApplicationReloadValues();

        Util.reloadBindings(allocationsGrid);
    }

    @SuppressWarnings("unchecked")
    private void loadSclassRowSatisfied() {
        try {
            List<org.zkoss.zul.Row> rows = allocationsGrid.getRows().getChildren();
            for (org.zkoss.zul.Row row : rows) {
                if (row.getValue() instanceof AllocationRow) {
                    if (!((AllocationRow) row.getValue()).isSatisfied()) {
                        row.setSclass("allocation-not-satisfied");
                    } else {
                        row.setSclass("allocation-satisfied");
                    }
                }
            }
        } catch (ClassCastException e) {
            throw new RuntimeException();
        }
    }

    private void loadAssignmentFunctionNames() {
        for (AllocationRow each : rows) {
            each.loadAssignmentFunctionName();
        }
    }

    private void loadEffortValues() {
        for (AllocationRow each : rows) {
            each.loadEffort();
        }
    }

    private void loadResourcesPerDay() {
        for (AllocationRow each : rows) {
            each.loadResourcesPerDay();
        }
    }

    private void loadDerivedAllocations() {
        for (AllocationRow each : rows) {
            each.reloadDerivedAllocationsGrid();
        }
    }

    void setApplyButton(Button applyButton) {
        this.applyButton = applyButton;
        this.applyButton.setDisabled(true);

        applyButtonListener = new EventListener() {
            @Override
            public void onEvent(Event event) {
                doApply();
                FormBinder.this.applyButton.setDisabled(true);
            }
        };

        Util.ensureUniqueListener(this.applyButton, Events.ON_CLICK, applyButtonListener);
    }

    EffortDuration getAssignedEffort() {
        EffortDuration result = effortInput.getEffortDurationValue();
        if (result == null) {
            throw new RuntimeException("assignedHoursComponent returns null");
        }

        return result;
    }

    Integer getWorkableDays() {
        return workableDaysAndDatesBinder.getValue();
    }

    void setDeleteButtonFor(Button deleteButton) {
        deleteButton.addEventListener(Events.ON_CLICK, new EventListener() {
            @Override
            public void onEvent(Event event) {
                applyButton.setDisabled(false);
            }
        });
    }

    void newAllocationAdded() {
        applyButton.setDisabled(false);
    }

    void markAssignedHoursMustBePositive() {
        throw new WrongValueException(effortInput, _("it must be greater than zero"));
    }

    void markRepeatedResources(List<Resource> resources) {
        messagesForUser.showMessage(Level.ERROR, _("{0} already assigned to resource allocation list",
                StringUtils.join(getResourcesDescriptions(resources), ", ")));
    }

    private List<String> getResourcesDescriptions(List<Resource> resources) {
        List<String> resourcesDescriptions = new ArrayList<>();
        for (Resource each : resources) {
            resourcesDescriptions.add(each.getShortDescription());
        }

        return resourcesDescriptions;
    }

    void markNoResourcesMatchedByCriterions(ResourceEnum resourceType, Collection<? extends Criterion> criterions) {
        messagesForUser.showMessage(Level.ERROR,
                _("there are no resources for required criteria: {0}. So the generic allocation can't be added",
                        Criterion.getCaptionFor(resourceType, criterions)));
    }

    void markThereisAlreadyAssignmentWith(ResourceEnum resourceType, Collection<? extends Criterion> criterions) {
        messagesForUser.showMessage(Level.ERROR, _("already exists an allocation for criteria {0}",
                Criterion.getCaptionFor(resourceType, criterions)));
    }

    void markNoEmptyResourcesPerDay(List<AllocationRow> rows) {
        Validate.isTrue(!rows.isEmpty());
        final String message = _("resources per day cannot be empty or less than zero");
        if (!recommendedAllocation) {
            AllocationRow first = rows.get(0);
            throw new WrongValueException(first.getIntendedResourcesPerDayInput(), message);
        } else {
            throw new WrongValueException(allResourcesPerDay, message);
        }
    }

    void setAllocationsGrid(Grid allocationsGrid) {
        this.allocationsGrid = allocationsGrid;
    }

    public void setMessagesForUser(IMessagesForUser messages) {
        this.messagesForUser = messages;
    }

    public void detach() {
        if (this.applyButton != null) {
            this.applyButton.removeEventListener(Events.ON_CLICK, applyButtonListener);
        }
        for (InputElement inputElement : inputsAssociatedWithOnChangeEnableApply) {
            inputElement.removeEventListener(Events.ON_CHANGE, onChangeEnableApply);
        }
    }

    void setRecommendedAllocation(Button recommendedAllocation) {
        this.btnRecommendedAllocation = recommendedAllocation;
        this.btnRecommendedAllocation.setDisabled(isAnyManual() || isTaskUpdatedFromTimesheets());

        Util.ensureUniqueListener(recommendedAllocation, Events.ON_CLICK, new EventListener() {
            @Override
            public void onEvent(Event event) {
                activatingRecommendedAllocation();
            }
        });
    }

    public EventListener getRecommendedAllocationListener() {
        return new EventListener() {
            @Override
            public void onEvent(Event event) {
                activatingRecommendedAllocation();
            }
        };
    }

    private void activatingRecommendedAllocation() {
        allocationRowsHandler.removeAll();
        ProportionalDistributor distributor = resourceAllocationModel.addDefaultAllocations();
        boolean recommendAllocationSuccessful = distributor != null;

        if (recommendAllocationSuccessful) {
            hoursDistributorForRecommendedAllocation = distributor;

            resourcesPerDayDistributorForRecommendedAllocation = ResourcesPerDay
                    .distributor(hoursDistributorForRecommendedAllocation);

            this.recommendedAllocation = true;
            disableIfNeededWorkerSearch();
            applyDisabledRules();
            effortInput.addEventListener(Events.ON_CHANGE, allHoursInputChange);
            allResourcesPerDay.addEventListener(Events.ON_CHANGE, allResourcesPerDayChange);
            resetStateForResourcesPerDayInputsWhenDoingRecommendedAllocation();
        }
        Util.reloadBindings(allocationsGrid);
    }

    private void resetStateForResourcesPerDayInputsWhenDoingRecommendedAllocation() {
        if (allResourcesPerDay.isDisabled()) {
            allResourcesPerDay.setValue((BigDecimal) null);
            AllocationRow.unknownResourcesPerDay(rows);
        } else {
            allResourcesPerDay.setValue(BigDecimal.ONE);
            distributeResourcesPerDayToRows();
            allResourcesPerDay.focus();
        }
    }

    private void distributeHoursFromTotalToRows() {
        EffortDuration value = effortInput.getEffortDurationValue();
        value = value != null ? value : zero();
        int[] seconds = hoursDistributorForRecommendedAllocation.distribute(value.getSeconds());
        assignEfforts(rows, asEfforts(seconds));
    }

    private EffortDuration[] asEfforts(int[] seconds) {
        EffortDuration[] result = new EffortDuration[seconds.length];

        for (int i = 0; i < result.length; i++) {
            result[i] = EffortDuration.seconds(seconds[i]);
        }

        return result;
    }

    private void distributeResourcesPerDayToRows() {
        BigDecimal total = allResourcesPerDay.getValue();
        total = total != null ? total : BigDecimal.ZERO;
        ResourcesPerDay[] forRows = resourcesPerDayDistributorForRecommendedAllocation
                .distribute(ResourcesPerDay.amount(total));
        AllocationRow.assignResourcesPerDay(rows, forRows);
    }

    void rowRemoved() {
        deactivatingRecommendedAllocation();
    }

    private void deactivatingRecommendedAllocation() {
        this.recommendedAllocation = false;
        effortInput.removeEventListener(Events.ON_CHANGE, allHoursInputChange);
        allResourcesPerDay.removeEventListener(Events.ON_CHANGE, allResourcesPerDayChange);
        applyDisabledRules();
        disableIfNeededWorkerSearch();
    }

    private void disableIfNeededWorkerSearch() {
        workerSearchTab.setDisabled(this.recommendedAllocation);
        newAllocationSelectorCombo.setDisabled(this.recommendedAllocation);
        advancedSearchButton.setDisabled(this.recommendedAllocation);
    }

    void setWorkerSearchTab(Tab workerSearchTab) {
        this.workerSearchTab = workerSearchTab;
        this.workerSearchTab.setDisabled(recommendedAllocation);
    }

    void setAdvancedSearchButton(Button advancedSearchButton) {
        this.advancedSearchButton = advancedSearchButton;
        this.advancedSearchButton.setDisabled(recommendedAllocation);
    }

    void setNewAllocationSelectorCombo(NewAllocationSelectorCombo newAllocationSelectorCombo) {
        this.newAllocationSelectorCombo = newAllocationSelectorCombo;
        this.newAllocationSelectorCombo.setDisabled(recommendedAllocation);
    }

    private void sumResourcesPerDayFromRowsAndAssignToAllResourcesPerDay() {
        if (allResourcesPerDay.isDisabled()) {
            allResourcesPerDay.setValue(sumResourcesPerDayFromInputs());
        }
    }

    private BigDecimal sumResourcesPerDayFromInputs() {
        BigDecimal sum = BigDecimal.ZERO;

        for (AllocationRow each : rows) {
            if (each.getIntendedResourcesPerDayInput().isValid()) {
                sum = sum.add(each.getResourcesPerDayFromInput().getAmount());
            }
        }

        return sum;
    }

    private BigDecimal sumAllTotalResourcesPerDay() {
        BigDecimal sum = BigDecimal.ZERO;

        for (AllocationRow each : rows) {
            sum = sum.add(each.getTotalResourcesPerDay().getAmount());
        }

        return sum;
    }

    private BigDecimal sumAllConsolidatedResourcesPerDay() {
        BigDecimal sum = BigDecimal.ZERO;

        for (AllocationRow each : rows) {
            sum = sum.add(each.getConsolidatedResourcesPerDay().getAmount());
        }

        return sum;
    }

    void setAllOriginalEffort(Label allOriginalEffort) {
        this.allOriginalEffort = allOriginalEffort;
    }

    public Label getAllOriginalEffort() {
        return allOriginalEffort;
    }

    void setAllTotalEffort(Label allTotalHours) {
        this.allTotalEffort = allTotalHours;
    }

    public Label getAllTotalEffort() {
        return allTotalEffort;
    }

    void setAllConsolidatedEffort(Label allConsolidatedEffort) {
        this.allConsolidatedEffort = allConsolidatedEffort;
    }

    public Label getAllConsolidatedEffort() {
        return allConsolidatedEffort;
    }

    void setAllTotalResourcesPerDay(Label allTotalResourcesPerDay) {
        this.allTotalResourcesPerDay = allTotalResourcesPerDay;
    }

    public Label getAllTotalResourcesPerDay() {
        return allTotalResourcesPerDay;
    }

    void setAllConsolidatedResourcesPerDay(Label allConsolidatedResourcesPerDay) {
        this.allConsolidatedResourcesPerDay = allConsolidatedResourcesPerDay;
    }

    public Label getAllConsolidatedResourcesPerDay() {
        return allConsolidatedResourcesPerDay;
    }

    private void loadAggregatedCalculations() {
        // Calculate aggregated values
        if (behaviour.allowMultipleSelection()) {
            allOriginalEffort.setValue(sumAllOriginalEffort(this.rows).toFormattedString());
            allTotalEffort.setValue(sumAllTotalEffort(this.rows).toFormattedString());
            allConsolidatedEffort.setValue(AllocationRow.sumAllConsolidatedEffort(this.rows).toFormattedString());
            allTotalResourcesPerDay.setValue(sumAllTotalResourcesPerDay().toString());
            allConsolidatedResourcesPerDay.setValue(sumAllConsolidatedResourcesPerDay().toString());
        }
    }

    public boolean allowMultipleSelection() {
        return behaviour.allowMultipleSelection();
    }

    public void cannotAllocateMoreThanOneResource(List<Resource> resources) {
        messagesForUser.showMessage(Level.ERROR,
                _("{0} could not be allocated. " + "Cannot allocate more than one resource",
                        Resource.getCaptionFor(resources)));
    }

    public void setBehaviour(ResourceAllocationBehaviour behaviour) {
        this.behaviour = behaviour;
    }

    boolean isAnyNotFlat() {
        for (AllocationRow allocationRow : allocationRowsHandler.getCurrentRows()) {
            if (allocationRow.isAssignmentFunctionNotFlat()) {
                return true;
            }
        }

        return false;
    }

    boolean isAnyManual() {
        for (AllocationRow allocationRow : allocationRowsHandler.getCurrentRows()) {
            if (allocationRow.isAssignmentFunctionManual()) {
                return true;
            }
        }

        return false;
    }

    boolean isTaskUpdatedFromTimesheets() {
        return allocationRowsHandler.isTaskUpdatedFromTimesheets();
    }

}