Package org.libreplan.web.planner

Source Code of org.libreplan.web.planner.TaskElementAdapter$Adapter

/*
* 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-2012 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.libreplan.web.planner;

import static org.libreplan.web.I18nHelper._;
import static org.libreplan.web.common.Util.addCurrencySymbol;
import static org.zkoss.ganttz.data.constraint.ConstraintOnComparableValues.biggerOrEqualThan;
import static org.zkoss.ganttz.data.constraint.ConstraintOnComparableValues.equalTo;
import static org.zkoss.ganttz.data.constraint.ConstraintOnComparableValues.lessOrEqualThan;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.math.Fraction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.Days;
import org.joda.time.Duration;
import org.joda.time.LocalDate;
import org.joda.time.Seconds;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.entities.ProgressType;
import org.libreplan.business.externalcompanies.daos.IExternalCompanyDAO;
import org.libreplan.business.labels.entities.Label;
import org.libreplan.business.orders.daos.IOrderElementDAO;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderStatusEnum;
import org.libreplan.business.orders.entities.SumChargedEffort;
import org.libreplan.business.orders.entities.SumExpenses;
import org.libreplan.business.planner.daos.IResourceAllocationDAO;
import org.libreplan.business.planner.daos.ITaskElementDAO;
import org.libreplan.business.planner.entities.Dependency;
import org.libreplan.business.planner.entities.Dependency.Type;
import org.libreplan.business.planner.entities.GenericResourceAllocation;
import org.libreplan.business.planner.entities.IMoneyCostCalculator;
import org.libreplan.business.planner.entities.ITaskPositionConstrained;
import org.libreplan.business.planner.entities.MoneyCostCalculator;
import org.libreplan.business.planner.entities.PositionConstraintType;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation.Direction;
import org.libreplan.business.planner.entities.SpecificResourceAllocation;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskElement.IDatesHandler;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.planner.entities.TaskPositionConstraint;
import org.libreplan.business.resources.daos.ICriterionDAO;
import org.libreplan.business.resources.daos.IResourcesSearcher;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;
import org.libreplan.web.planner.order.PlanningStateCreator.PlanningState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.zkoss.ganttz.IDatesMapper;
import org.zkoss.ganttz.ProjectStatusEnum;
import org.zkoss.ganttz.adapters.DomainDependency;
import org.zkoss.ganttz.adapters.IAdapterToTaskFundamentalProperties;
import org.zkoss.ganttz.data.DependencyType;
import org.zkoss.ganttz.data.GanttDate;
import org.zkoss.ganttz.data.GanttDate.Cases;
import org.zkoss.ganttz.data.GanttDate.CustomDate;
import org.zkoss.ganttz.data.GanttDate.LocalDateBased;
import org.zkoss.ganttz.data.ITaskFundamentalProperties;
import org.zkoss.ganttz.data.constraint.Constraint;
import org.zkoss.ganttz.util.ReentranceGuard;
import org.zkoss.ganttz.util.ReentranceGuard.IReentranceCases;

/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class TaskElementAdapter {

    private static final Log LOG = LogFactory.getLog(TaskElementAdapter.class);

    public static List<Constraint<GanttDate>> getStartConstraintsFor(
            TaskElement taskElement, LocalDate orderInitDate) {
        if (taskElement instanceof ITaskPositionConstrained) {
            ITaskPositionConstrained task = (ITaskPositionConstrained) taskElement;
            TaskPositionConstraint startConstraint = task
                    .getPositionConstraint();
            final PositionConstraintType constraintType = startConstraint
                    .getConstraintType();
            switch (constraintType) {
            case AS_SOON_AS_POSSIBLE:
                if (orderInitDate != null) {
                    return Collections
                            .singletonList(biggerOrEqualThan(toGantt(orderInitDate)));
                }
                return Collections.emptyList();
            case START_IN_FIXED_DATE:
                return Collections
                        .singletonList(equalTo(toGantt(startConstraint
                                .getConstraintDate())));
            case START_NOT_EARLIER_THAN:
                return Collections
                        .singletonList(biggerOrEqualThan(toGantt(startConstraint
                                .getConstraintDate())));
            }
        }
        return Collections.emptyList();
    }

    public static List<Constraint<GanttDate>> getEndConstraintsFor(
            TaskElement taskElement, LocalDate deadline) {
        if (taskElement instanceof ITaskPositionConstrained) {
            ITaskPositionConstrained task = (ITaskPositionConstrained) taskElement;
            TaskPositionConstraint endConstraint = task.getPositionConstraint();
            PositionConstraintType type = endConstraint.getConstraintType();
            switch (type) {
            case AS_LATE_AS_POSSIBLE:
                if (deadline != null) {
                    return Collections
                            .singletonList(lessOrEqualThan(toGantt(deadline)));
                }
            case FINISH_NOT_LATER_THAN:
                GanttDate date = toGantt(endConstraint.getConstraintDate());
                return Collections.singletonList(lessOrEqualThan(date));
            }
        }
        return Collections.emptyList();
    }

    public static GanttDate toGantt(IntraDayDate date) {
        return toGantt(date, null);
    }

    public static GanttDate toGantt(IntraDayDate date,
            EffortDuration dayCapacity) {
        if (date == null) {
            return null;
        }
        if (dayCapacity == null) {
            // a sensible default
            dayCapacity = EffortDuration.hours(8);
        }
        return new GanttDateAdapter(date, dayCapacity);
    }

    public static GanttDate toGantt(LocalDate date) {
        if (date == null) {
            return null;
        }
        return GanttDate.createFrom(date);
    }

    public static IntraDayDate toIntraDay(GanttDate date) {
        if (date == null) {
            return null;
        }
        return date.byCases(new Cases<GanttDateAdapter, IntraDayDate>(
                GanttDateAdapter.class) {

            @Override
            public IntraDayDate on(LocalDateBased localDate) {
                return IntraDayDate.startOfDay(localDate.getLocalDate());
            }

            @Override
            protected IntraDayDate onCustom(GanttDateAdapter customType) {
                return customType.date;
            }
        });
    }

    public IAdapterToTaskFundamentalProperties<TaskElement> createForCompany(
            Scenario currentScenario) {
        Adapter result = new Adapter();
        result.useScenario(currentScenario);
        result.setPreventCalculateResourcesText(true);
        return result;
    }

    public IAdapterToTaskFundamentalProperties<TaskElement> createForOrder(
            Scenario currentScenario, Order order, PlanningState planningState) {
        Adapter result = new Adapter(planningState);
        result.useScenario(currentScenario);
        result.setInitDate(asLocalDate(order.getInitDate()));
        result.setDeadline(asLocalDate(order.getDeadline()));
        return result;
    }

    private LocalDate asLocalDate(Date date) {
        return date != null ? LocalDate.fromDateFields(date) : null;
    }

    @Autowired
    private IAdHocTransactionService transactionService;

    private final ReentranceGuard reentranceGuard = new ReentranceGuard();

    @Autowired
    private IOrderElementDAO orderElementDAO;

    @Autowired
    private ITaskElementDAO taskDAO;

    @Autowired
    private ICriterionDAO criterionDAO;

    @Autowired
    private IResourceAllocationDAO resourceAllocationDAO;

    @Autowired
    private IExternalCompanyDAO externalCompanyDAO;

    @Autowired
    private IResourcesSearcher searcher;

    @Autowired
    private IConfigurationDAO configurationDAO;

    @Autowired
    private IMoneyCostCalculator moneyCostCalculator;

    static class GanttDateAdapter extends CustomDate {

        private static final int DAY_MILLISECONDS = (int) Days.days(1)
                .toStandardDuration().getMillis();

        private final IntraDayDate date;
        private final Duration workingDayDuration;

        GanttDateAdapter(IntraDayDate date, EffortDuration capacityForDay) {
            this.date = date;
            this.workingDayDuration = toMilliseconds(capacityForDay);
        }

        protected int compareToCustom(CustomDate customType) {
            if (customType instanceof GanttDateAdapter) {
                GanttDateAdapter other = (GanttDateAdapter) customType;
                return this.date.compareTo(other.date);
            }
            throw new RuntimeException("incompatible type: " + customType);
        }

        protected int compareToLocalDate(LocalDate localDate) {
            return this.date.compareTo(localDate);
        }

        public IntraDayDate getDate() {
            return date;
        }

        @Override
        public Date toDayRoundedDate() {
            return date.toDateTimeAtStartOfDay().toDate();
        }

        @Override
        public LocalDate toLocalDate() {
            return date.getDate();
        }

        @Override
        public LocalDate asExclusiveEnd() {
            return date.asExclusiveEnd();
        }

        @Override
        protected boolean isEqualsToCustom(CustomDate customType) {
            if (customType instanceof GanttDateAdapter) {
                GanttDateAdapter other = (GanttDateAdapter) customType;
                return this.date.equals(other.date);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return date.hashCode();
        }

        @Override
        public int toPixels(IDatesMapper datesMapper) {
            int pixesUntilDate = datesMapper.toPixels(this.date.getDate());
            EffortDuration effortDuration = date.getEffortDuration();
            Duration durationInDay = calculateDurationInDayFor(effortDuration);
            int pixelsInsideDay = datesMapper.toPixels(durationInDay);
            return pixesUntilDate + pixelsInsideDay;
        }

        private Duration calculateDurationInDayFor(EffortDuration effortDuration) {
            if (workingDayDuration.getStandardSeconds() == 0) {
                return Duration.ZERO;
            }
            Fraction fraction = fractionOfWorkingDayFor(effortDuration);
            try {
                return new Duration(fraction.multiplyBy(
                        Fraction.getFraction(DAY_MILLISECONDS, 1)).intValue());
            } catch (ArithmeticException e) {
                // if fraction overflows use floating point arithmetic
                return new Duration(
                        (int) (fraction.doubleValue() * DAY_MILLISECONDS));
            }
        }

        @SuppressWarnings("unchecked")
        private Fraction fractionOfWorkingDayFor(EffortDuration effortDuration) {
            Duration durationInDay = toMilliseconds(effortDuration);
            // cast to int is safe because there are not enough seconds in
            // day
            // to overflow
            Fraction fraction = Fraction.getFraction(
                    (int) durationInDay.getStandardSeconds(),
                    (int) workingDayDuration.getStandardSeconds());
            return (Fraction) Collections.min(Arrays.asList(fraction,
                    Fraction.ONE));
        }

        private static Duration toMilliseconds(EffortDuration duration) {
            return Seconds.seconds(duration.getSeconds()).toStandardDuration();
        }
    }

    /**
     * Responsible of adaptating a {@link TaskElement} into a
     * {@link ITaskFundamentalProperties} <br />
     * @author Óscar González Fernández <ogonzalez@igalia.com>
     */
    public class Adapter implements
            IAdapterToTaskFundamentalProperties<TaskElement> {

        private Scenario scenario;

        private LocalDate initDate;

        private LocalDate deadline;

        private boolean preventCalculateResourcesText = false;

        private final PlanningState planningState;

        private void useScenario(Scenario scenario) {
            this.scenario = scenario;
        }

        private void setInitDate(LocalDate initDate) {
            this.initDate = initDate;
        }

        private void setDeadline(LocalDate deadline) {
            this.deadline = deadline;
        }

        public boolean isPreventCalculateResourcesText() {
            return preventCalculateResourcesText;
        }

        public void setPreventCalculateResourcesText(
                boolean preventCalculateResourcesText) {
            this.preventCalculateResourcesText = preventCalculateResourcesText;
        }

        public Adapter() {
            this(null);
        }

        public Adapter(PlanningState planningState) {
            this.planningState = planningState;
        }

        private class TaskElementWrapper implements ITaskFundamentalProperties {

            private final TaskElement taskElement;

            private final Scenario currentScenario;

            protected TaskElementWrapper(Scenario currentScenario,
                    TaskElement taskElement) {
                Validate.notNull(currentScenario);
                this.currentScenario = currentScenario;
                this.taskElement = taskElement;
            }

            private final IUpdatablePosition position = new IUpdatablePosition() {

                @Override
                public void setEndDate(GanttDate endDate) {
                    stepsBeforePossibleReallocation();
                    getDatesHandler(taskElement).moveEndTo(toIntraDay(endDate));
                }

                @Override
                public void setBeginDate(final GanttDate beginDate) {
                    stepsBeforePossibleReallocation();
                    getDatesHandler(taskElement).moveTo(toIntraDay(beginDate));
                }

                @Override
                public void resizeTo(final GanttDate endDate) {
                    stepsBeforePossibleReallocation();
                    updateTaskPositionConstraint(endDate);
                    getDatesHandler(taskElement).resizeTo(toIntraDay(endDate));
                }

                private void stepsBeforePossibleReallocation() {
                    taskDAO.reattach(taskElement);
                }

                @Override
                public void moveTo(GanttDate newStart) {
                    if (taskElement instanceof ITaskPositionConstrained) {
                        ITaskPositionConstrained task = (ITaskPositionConstrained) taskElement;
                        GanttDate newEnd = inferEndFrom(newStart);
                        if (task.getPositionConstraint()
                                .isConstraintAppliedToStart()) {
                            setBeginDate(newStart);
                        } else {
                            setEndDate(newEnd);
                        }
                        task.explicityMoved(toIntraDay(newStart), toIntraDay(newEnd));
                    }
                }
            };

            @Override
            public void setName(String name) {
                taskElement.setName(name);
            }

            @Override
            public void setNotes(String notes) {
                taskElement.setNotes(notes);
            }

            @Override
            public String getName() {
                return taskElement.getName();
            }

            @Override
            public String getCode() {
                return taskElement.getCode();
            }

            @Override
            public String getProjectCode() {
                return taskElement.getProjectCode();
            }

            @Override
            public String getNotes() {
                return taskElement.getNotes();
            }

            @Override
            public GanttDate getBeginDate() {
                IntraDayDate start = taskElement.getIntraDayStartDate();
                return toGantt(start);
            }

            private GanttDate toGantt(IntraDayDate date) {
                BaseCalendar calendar = taskElement.getCalendar();
                if (calendar == null) {
                    return TaskElementAdapter.toGantt(date);
                }
                return TaskElementAdapter.toGantt(date, calendar
                        .getCapacityOn(PartialDay.wholeDay(date.getDate())));
            }

            @Override
            public void doPositionModifications(
                    final IModifications modifications) {
                reentranceGuard.entranceRequested(new IReentranceCases() {

                    @Override
                    public void ifNewEntrance() {
                        transactionService.runOnReadOnlyTransaction(asTransaction(modifications));
                    }

                    IOnTransaction<Void> asTransaction(
                            final IModifications modifications) {
                        return new IOnTransaction<Void>() {

                            @Override
                            public Void execute() {
                                if (planningState != null) {
                                    planningState
                                            .reassociateResourcesWithSession();
                                }
                                modifications.doIt(position);
                                return null;
                            }
                        };
                    }

                    @Override
                    public void ifAlreadyInside() {
                        modifications.doIt(position);
                    }
                });
            }

            @Override
            public GanttDate getEndDate() {
                return toGantt(taskElement.getIntraDayEndDate());
            }

            IDatesHandler getDatesHandler(TaskElement taskElement) {
                return taskElement.getDatesHandler(currentScenario, searcher);
            }

            private void updateTaskPositionConstraint(GanttDate endDate) {
                if (taskElement instanceof ITaskPositionConstrained) {
                    ITaskPositionConstrained task = (ITaskPositionConstrained) taskElement;
                    PositionConstraintType constraintType = task
                            .getPositionConstraint().getConstraintType();
                    if (constraintType
                            .compareTo(PositionConstraintType.FINISH_NOT_LATER_THAN) == 0
                            || constraintType
                                    .compareTo(PositionConstraintType.AS_LATE_AS_POSSIBLE) == 0) {
                        task.explicityMoved(taskElement.getIntraDayStartDate(),
                                toIntraDay(endDate));
                    }
                }
            }

            @Override
            public GanttDate getHoursAdvanceBarEndDate() {
                return calculateLimitDateProportionalToTaskElementSize(getHoursAdvanceBarPercentage());
            }

            @Override
            public BigDecimal getHoursAdvanceBarPercentage() {
                OrderElement orderElement = taskElement.getOrderElement();
                if (orderElement == null) {
                    return BigDecimal.ZERO;
                }

                EffortDuration totalChargedEffort = orderElement
                        .getSumChargedEffort() != null ? orderElement
                        .getSumChargedEffort().getTotalChargedEffort()
                        : EffortDuration.zero();

                EffortDuration estimatedEffort = taskElement.getSumOfAssignedEffort();

                if(estimatedEffort.isZero()) {
                    estimatedEffort = EffortDuration.hours(orderElement.getWorkHours());
                    if(estimatedEffort.isZero()) {
                        return BigDecimal.ZERO;
                    }
                }
                return new BigDecimal(totalChargedEffort.divivedBy(
                        estimatedEffort).doubleValue()).setScale(2,
                        RoundingMode.HALF_UP);
            }

            @Override
            public GanttDate getMoneyCostBarEndDate() {
                return calculateLimitDateProportionalToTaskElementSize(getMoneyCostBarPercentage());
            }

            private GanttDate calculateLimitDateProportionalToTaskElementSize(
                    BigDecimal proportion) {
                if (proportion.compareTo(BigDecimal.ZERO) == 0) {
                    return getBeginDate();
                }

                IntraDayDate start = taskElement.getIntraDayStartDate();
                IntraDayDate end = taskElement.getIntraDayEndDate();

                EffortDuration effortBetween = start.effortUntil(end);
                int seconds = new BigDecimal(effortBetween.getSeconds())
                        .multiply(proportion).toBigInteger().intValue();
                return TaskElementAdapter.toGantt(
                        start.addEffort(EffortDuration.seconds(seconds)),
                        EffortDuration.hours(8));
            }

            @Override
            public BigDecimal getMoneyCostBarPercentage() {
                return MoneyCostCalculator.getMoneyCostProportion(
                        getMoneyCost(), getBudget());
            }

            private BigDecimal getBudget() {
                if ((taskElement == null)
                        || (taskElement.getOrderElement() == null)) {
                    return BigDecimal.ZERO;
                }
                return taskElement.getOrderElement().getBudget();
            }

            private BigDecimal getTotalCalculatedBudget() {
                if ((taskElement == null)
                        || (taskElement.getOrderElement() == null)) {
                    return BigDecimal.ZERO;
                }
                return transactionService
                        .runOnReadOnlyTransaction(new IOnTransaction<BigDecimal>() {

                            @Override
                            public BigDecimal execute() {
                                return taskElement.getOrderElement()
                                        .getTotalBudget();
                            }
                        });
            }

            private BigDecimal getMoneyCost() {
                if ((taskElement == null)
                        || (taskElement.getOrderElement() == null)) {
                    return BigDecimal.ZERO;
                }
                return transactionService
                        .runOnReadOnlyTransaction(new IOnTransaction<BigDecimal>() {

                            @Override
                            public BigDecimal execute() {
                                return moneyCostCalculator.getTotalMoneyCost(taskElement
                                        .getOrderElement());
                            }
                        });
            }

            private BigDecimal getHoursMoneyCost() {
                if ((taskElement == null) || (taskElement.getOrderElement() == null)) {
                    return BigDecimal.ZERO;
                }

                return transactionService
                        .runOnReadOnlyTransaction(new IOnTransaction<BigDecimal>() {
                            @Override
                            public BigDecimal execute() {
                                return moneyCostCalculator.getHoursMoneyCost(taskElement.getOrderElement());
                            }
                        });
            }

            private BigDecimal getExpensesMoneyCost() {
                if ((taskElement == null) || (taskElement.getOrderElement() == null)) {
                    return BigDecimal.ZERO;
                }

                return transactionService
                        .runOnReadOnlyTransaction(new IOnTransaction<BigDecimal>() {
                            @Override
                            public BigDecimal execute() {
                                return moneyCostCalculator.getExpensesMoneyCost(taskElement
                                        .getOrderElement());
                            }
                        });
            }

            @Override
            public GanttDate getAdvanceBarEndDate(String progressType) {
                return getAdvanceBarEndDate(ProgressType.asEnum(progressType));
            }

            private GanttDate getAdvanceBarEndDate(ProgressType progressType) {
                BigDecimal advancePercentage = BigDecimal.ZERO;
                if (taskElement.getOrderElement() != null) {
                    advancePercentage = taskElement
                            .getAdvancePercentage(progressType);
                }
                return getAdvanceBarEndDate(advancePercentage);
            }

            @Override
            public GanttDate getAdvanceBarEndDate() {
                return getAdvanceBarEndDate(getAdvancePercentage());
            }

            private boolean isTaskRoot(TaskElement taskElement) {
                return taskElement instanceof TaskGroup
                        && taskElement.getParent() == null;
            }

            private ProgressType getProgressTypeFromConfiguration() {
                return transactionService
                        .runOnReadOnlyTransaction(new IOnTransaction<ProgressType>() {
                            @Override
                            public ProgressType execute() {
                                return configurationDAO.getConfiguration()
                                        .getProgressType();
                            }
                        });
            }

            private GanttDate getAdvanceBarEndDate(BigDecimal advancePercentage) {
                return calculateLimitDateProportionalToTaskElementSize(advancePercentage);
            }

            @Override
            public String getTooltipText() {
                if (taskElement.isMilestone()
                        || taskElement.getOrderElement() == null) {
                    return "";
                }
                return transactionService
                        .runOnReadOnlyTransaction(new IOnTransaction<String>() {

                            @Override
                            public String execute() {
                                orderElementDAO.reattach(taskElement
                                        .getOrderElement());
                                return buildTooltipText();
                            }
                        });
            }

            @Override
            public String getLabelsText() {
                if (taskElement.isMilestone()
                        || taskElement.getOrderElement() == null) {
                    return "";
                }
                return transactionService
                        .runOnReadOnlyTransaction(new IOnTransaction<String>() {

                            @Override
                            public String execute() {
                                orderElementDAO.reattach(taskElement
                                        .getOrderElement());
                                return buildLabelsText();
                            }
                        });
            }

            @Override
            public String getResourcesText() {
                if (isPreventCalculateResourcesText()
                        || taskElement.getOrderElement() == null) {
                    return "";
                }
                try {
                    return transactionService
                            .runOnAnotherReadOnlyTransaction(new IOnTransaction<String>() {

                                @Override
                                public String execute() {
                                    orderElementDAO.reattach(taskElement
                                            .getOrderElement());
                                    if (taskElement.isSubcontracted()) {
                                    externalCompanyDAO.reattach(taskElement
                                                .getSubcontractedCompany());
                                    }
                                    return buildResourcesText();
                                }
                            });
                } catch (Exception e) {
                    LOG.error("error calculating resources text", e);
                    return "";
                }
            }

            private Set<Label> getLabelsFromElementAndPredecesors(
                    OrderElement order) {
                if (order != null) {
                    if (order.getParent() == null) {
                        return order.getLabels();
                    } else {
                        HashSet<Label> labels = new HashSet<Label>(
                                order.getLabels());
                        labels.addAll(getLabelsFromElementAndPredecesors(order
                                .getParent()));
                        return labels;
                    }
                }
                return new HashSet<Label>();
            }

            private String buildLabelsText() {
                List<String> result = new ArrayList<String>();

                if (taskElement.getOrderElement() != null) {
                    Set<Label> labels = getLabelsFromElementAndPredecesors(taskElement
                            .getOrderElement());

                    for (Label label : labels) {
                        String representation = label.getName();
                        if (!result.contains(representation)) {
                            result.add(representation);
                        }
                    }
                }

                Collections.sort(result);
                return StringUtils.join(result, ", ");
            }

            private String buildResourcesText() {
                List<String> result = new ArrayList<String>();
                for (ResourceAllocation<?> each : taskElement
                        .getSatisfiedResourceAllocations()) {
                    if (each instanceof SpecificResourceAllocation) {
                        for (Resource r : each.getAssociatedResources()) {
                            String representation = r.getName();
                            if (!result.contains(representation)) {
                                result.add(representation);
                            }
                        }
                    } else {
                        String representation = extractRepresentationForGeneric((GenericResourceAllocation) each);
                        if (!result.contains(representation)) {
                            result.add(representation);
                        }
                    }
                }
                if (taskElement.isSubcontracted()) {
                    result.add(taskElement.getSubcontractionName());
                }
                Collections.sort(result);
                return StringUtils.join(result, "; ");
            }

            private String extractRepresentationForGeneric(
                    GenericResourceAllocation generic) {
                if (!generic.isNewObject()) {
                    resourceAllocationDAO.reattach(generic);
                }
                Set<Criterion> criterions = generic.getCriterions();
                List<String> forCriterionRepresentations = new ArrayList<String>();
                if (!criterions.isEmpty()) {
                    for (Criterion c : criterions) {
                        criterionDAO.reattachUnmodifiedEntity(c);
                        forCriterionRepresentations.add(c.getName());
                    }
                } else {
                    forCriterionRepresentations.add((_("All workers")));
                }
                return "["
                        + StringUtils.join(forCriterionRepresentations, ", ")
                        + "]";
            }

            @Override
            public String updateTooltipText() {
                return buildTooltipText();
            }

            @Override
            public String updateTooltipText(String progressType) {
                return buildTooltipText(ProgressType.asEnum(progressType));
            }

            @Override
            public BigDecimal getAdvancePercentage() {
                if (taskElement != null) {
                    BigDecimal advancePercentage;
                    if (isTaskRoot(taskElement)) {
                        ProgressType progressType = getProgressTypeFromConfiguration();
                        advancePercentage = taskElement
                                .getAdvancePercentage(progressType);
                    } else {
                        advancePercentage = taskElement.getAdvancePercentage();
                    }
                    return advancePercentage;
                }
                return new BigDecimal(0);
            }

            private String buildTooltipText() {
                return buildTooltipText(asPercentage(getAdvancePercentage()));
            }

            private BigDecimal asPercentage(BigDecimal value) {
                return value.multiply(BigDecimal.valueOf(100)).setScale(2,
                        RoundingMode.DOWN);
            }

            private String buildTooltipText(BigDecimal progressPercentage) {
                StringBuilder result = new StringBuilder();
                result.append("<strong>" + getName() + "</strong><br/>");
                result.append(_("Progress") + ": ").append(progressPercentage)
                        .append("% , ");

                result.append(_("Hours invested") + ": ")
                        .append(getHoursAdvanceBarPercentage().multiply(
                                new BigDecimal(100))).append("% <br/>");

                if (taskElement.getOrderElement() instanceof Order) {
                    result.append(_("State") + ": ").append(getOrderState());
                } else {
                    String budget = addCurrencySymbol(getTotalCalculatedBudget());
                    String moneyCost = addCurrencySymbol(getMoneyCost());

                    String costHours = addCurrencySymbol(getHoursMoneyCost());
                    String costExpenses = addCurrencySymbol(getExpensesMoneyCost());
                    result.append(
                            _("Budget: {0}, Consumed: {1} ({2}%)", budget, moneyCost,
                                    getMoneyCostBarPercentage().multiply(new BigDecimal(100))))
                            .append("<br/>");
                    result.append(
_(
                            "Hours cost: {0}, Expenses cost: {1}",
                            costHours, costExpenses));
                }

                String labels = buildLabelsText();
                if (!labels.equals("")) {
                    result.append("<div class='tooltip-labels'>" + _("Labels")
                            + ": " + labels + "</div>");
                }
                return result.toString();
            }

            private String buildTooltipText(ProgressType progressType) {
                return buildTooltipText(asPercentage(taskElement
                        .getAdvancePercentage(progressType)));
            }

            private String getOrderState() {
                String cssClass;
                OrderStatusEnum state = taskElement.getOrderElement()
                        .getOrder().getState();

                if (Arrays.asList(OrderStatusEnum.ACCEPTED,
                        OrderStatusEnum.OFFERED, OrderStatusEnum.STARTED,
                        OrderStatusEnum.OUTSOURCED).contains(
                        state)) {
                    if (taskElement.getAssignedStatus() == "assigned") {
                        cssClass = "order-open-assigned";
                    } else {
                        cssClass = "order-open-unassigned";
                    }
                } else {
                    cssClass = "order-closed";
                }
                return "<font class='" + cssClass + "'>" + _(state.toString())
                        + "</font>";
            }

            @Override
            public List<Constraint<GanttDate>> getStartConstraints() {
                return getStartConstraintsFor(this.taskElement, initDate);
            }

            @Override
            public List<Constraint<GanttDate>> getEndConstraints() {
                return getEndConstraintsFor(this.taskElement, deadline);
            }

            @Override
            public List<Constraint<GanttDate>> getCurrentLengthConstraint() {
                if (taskElement instanceof Task) {
                    Task task = (Task) taskElement;
                    if (task.getAllocationDirection() == Direction.FORWARD) {
                        return Collections
                                .singletonList(biggerOrEqualThan(getEndDate()));
                    }
                }
                return Collections.emptyList();
            }

            private GanttDate inferEndFrom(GanttDate newStart) {
                if (taskElement instanceof Task) {
                    Task task = (Task) taskElement;
                    return toGantt(task
                            .calculateEndKeepingLength(toIntraDay(newStart)));
                }
                return newStart;
            }

            @Override
            public Date getDeadline() {
                LocalDate deadline = taskElement.getDeadline();
                if (deadline == null) {
                    return null;
                }
                return deadline.toDateTimeAtStartOfDay().toDate();
            }

            @Override
            public void setDeadline(Date date) {
                if (date != null) {
                    taskElement.setDeadline(LocalDate.fromDateFields(date));
                } else {
                    taskElement.setDeadline(null);
                }
            }

            @Override
            public GanttDate getConsolidatedline() {
                if (!taskElement.isLeaf() || !taskElement.hasConsolidations()) {
                    return null;
                }
                LocalDate consolidatedline = ((Task) taskElement)
                        .getFirstDayNotConsolidated().getDate();
                return TaskElementAdapter.toGantt(consolidatedline);
            }

            @Override
            public boolean isSubcontracted() {
                return taskElement.isSubcontracted();
            }

            @Override
            public boolean isLimiting() {
                return taskElement.isLimiting();
            }

            @Override
            public boolean isLimitingAndHasDayAssignments() {
                return taskElement.isLimitingAndHasDayAssignments();
            }

            public boolean hasConsolidations() {
                return taskElement.hasConsolidations();
            }

            @Override
            public boolean canBeExplicitlyResized() {
                return taskElement.canBeExplicitlyResized();
            }

            @Override
            public String getAssignedStatus() {
                return taskElement.getAssignedStatus();
            }

            @Override
            public boolean isFixed() {
                return taskElement.isLimitingAndHasDayAssignments()
                        || taskElement.hasConsolidations()
                        || taskElement.isUpdatedFromTimesheets();
            }

            @Override
            public boolean isManualAnyAllocation() {
                return taskElement.isTask()
                        && ((Task) taskElement).isManualAnyAllocation();
            }

            @Override
            public boolean belongsClosedProject() {
                return taskElement.belongsClosedProject();
            }

            @Override
            public boolean isRoot() {
                return taskElement.isRoot();
            }

            @Override
            public boolean isUpdatedFromTimesheets() {
                return taskElement.isUpdatedFromTimesheets();
            }

            @Override
            public Date getFirstTimesheetDate() {
                OrderElement orderElement = taskElement.getOrderElement();
                if (orderElement != null) {
                    return orderElement.getFirstTimesheetDate();
                }
                return null;
            }

            @Override
            public Date getLastTimesheetDate() {
                OrderElement orderElement = taskElement.getOrderElement();
                if (orderElement != null) {
                    return orderElement.getLastTimesheetDate();
                }
                return null;
            }

            @Override
            public ProjectStatusEnum getProjectHoursStatus() {

                if (taskElement.isTask()) {
                    return getProjectHourStatus(taskElement.getOrderElement());
                }

                List<TaskElement> taskElements = taskElement.getAllChildren();

                ProjectStatusEnum status = ProjectStatusEnum.AS_PLANNED;
                ProjectStatusEnum highestStatus = null;

                for (TaskElement taskElement : taskElements) {

                    if (!taskElement.isTask()) {
                        continue;
                    }

                    status = getProjectHourStatus(taskElement.getOrderElement());

                    if (status == ProjectStatusEnum.MARGIN_EXCEEDED) {
                        highestStatus = ProjectStatusEnum.MARGIN_EXCEEDED;
                        break;
                    }

                    if (status == ProjectStatusEnum.WITHIN_MARGIN) {
                        highestStatus = ProjectStatusEnum.WITHIN_MARGIN;
                    }

                }

                if (highestStatus != null) {
                    status = highestStatus;
                }

                return status;
            }

            /**
             * Returns {@link ProjectStatusEnum} for the specified
             * <code>orderElement</code>
             *
             * @param orderElement
             */
            private ProjectStatusEnum getProjectHourStatus(OrderElement orderElement) {
                EffortDuration sumChargedEffort = getSumChargedEffort(orderElement);
                EffortDuration estimatedEffort = getEstimatedEffort(orderElement);
                if (sumChargedEffort.isZero()
                        || sumChargedEffort.compareTo(estimatedEffort) <= 0) {
                    return ProjectStatusEnum.AS_PLANNED;
                }

                EffortDuration withMarginEstimatedHours = orderElement
                        .getWithMarginCalculatedHours();

                if (estimatedEffort.compareTo(sumChargedEffort) < 0
                        && sumChargedEffort.compareTo(withMarginEstimatedHours) <= 0) {
                    return ProjectStatusEnum.WITHIN_MARGIN;
                }
                return ProjectStatusEnum.MARGIN_EXCEEDED;

            }

            /**
             * Returns sum charged effort for the specified
             * <code>orderElement</code>
             *
             * @param orderElement
             */
            private EffortDuration getSumChargedEffort(OrderElement orderElement) {
                SumChargedEffort sumChargedEffort = orderElement
                        .getSumChargedEffort();
                EffortDuration totalChargedEffort = sumChargedEffort != null ? sumChargedEffort
                        .getTotalChargedEffort() : EffortDuration.zero();
                return totalChargedEffort;
            }

            /**
             * Returns the estimated effort for the specified
             * <code>orderElement</code>
             *
             * @param orderElement
             */
            private EffortDuration getEstimatedEffort(OrderElement orderElement) {
                return EffortDuration.fromHoursAsBigDecimal(new BigDecimal(
                        orderElement.getWorkHours()).setScale(2));
            }

            @Override
            public ProjectStatusEnum getProjectBudgetStatus() {

                if (taskElement.isTask()) {
                    return getProjectBudgetStatus(taskElement.getOrderElement());
                }

                List<TaskElement> taskElements = taskElement.getAllChildren();

                ProjectStatusEnum status = ProjectStatusEnum.AS_PLANNED;
                ProjectStatusEnum highestStatus = null;

                for (TaskElement taskElement : taskElements) {

                    if (!taskElement.isTask()) {
                        continue;
                    }

                    status = getProjectBudgetStatus(taskElement
                            .getOrderElement());

                    if (status == ProjectStatusEnum.MARGIN_EXCEEDED) {
                        highestStatus = ProjectStatusEnum.MARGIN_EXCEEDED;
                        break;
                    }

                    if (status == ProjectStatusEnum.WITHIN_MARGIN) {
                        highestStatus = ProjectStatusEnum.WITHIN_MARGIN;
                    }

                }

                if (highestStatus != null) {
                    status = highestStatus;
                }

                return status;
            }

            /**
             * Returns {@link ProjectStatusEnum} for the specified
             * <code>orderElement</code>
             *
             * @param orderElement
             */
            private ProjectStatusEnum getProjectBudgetStatus(
                    OrderElement orderElement) {
                BigDecimal budget = orderElement.getBudget();
                BigDecimal totalExpense = getTotalExpense(orderElement);
                BigDecimal withMarginCalculatedBudget = orderElement
                        .getWithMarginCalculatedBudget();

                if (totalExpense.compareTo(budget) <= 0) {
                    return ProjectStatusEnum.AS_PLANNED;
                }

                if (budget.compareTo(totalExpense) < 0
                        && totalExpense.compareTo(withMarginCalculatedBudget) <= 0) {
                    return ProjectStatusEnum.WITHIN_MARGIN;
                }

                return ProjectStatusEnum.MARGIN_EXCEEDED;
            }

            /**
             * Returns total expense for the specified <code>orderElement</code>
             *
             * @param orderElement
             */
            public BigDecimal getTotalExpense(OrderElement orderElement) {
                BigDecimal total = BigDecimal.ZERO;

                SumExpenses sumExpenses = orderElement.getSumExpenses();

                if (sumExpenses != null) {
                    BigDecimal directExpenes = sumExpenses
                            .getTotalDirectExpenses();
                    BigDecimal indirectExpense = sumExpenses
                            .getTotalIndirectExpenses();

                    if (directExpenes != null) {
                        total = total.add(directExpenes);
                    }

                    if (indirectExpense != null) {
                        total = total.add(indirectExpense);
                    }
                }
                return total;
            }

            @Override
            public String getTooltipTextForProjectHoursStatus() {
                if (taskElement.isTask()) {
                    return buildHoursTooltipText(taskElement.getOrderElement());
                }
                return null;
            }

            @Override
            public String getTooltipTextForProjectBudgetStatus() {
                if (taskElement.isTask()) {
                    return buildBudgetTooltipText(taskElement.getOrderElement());
                }
                return null;
            }

            /**
             * Builds hours tooltiptext for the specified
             * <code>orderElement</code>
             *
             * @param orderElement
             */
            private String buildHoursTooltipText(OrderElement orderElement) {
                StringBuilder result = new StringBuilder();
                Integer margin = orderElement.getOrder().getHoursMargin() != null ? orderElement
                        .getOrder().getHoursMargin() : 0;

                result.append(_("Hours-status") + "\n");
                result.append(_("Project margin: {0}% ({1} hours)={2} hours",
                                margin, orderElement.getWorkHours(),
                        orderElement.getWithMarginCalculatedHours()));

                String totalEffortHours = orderElement.getEffortAsString();

                result.append(_(". Already registered: {0} hours", totalEffortHours));
                return result.toString();
            }

            private String buildBudgetTooltipText(OrderElement orderElement) {
                StringBuilder result = new StringBuilder();
                Integer margin = orderElement.getOrder().getBudgetMargin() != null ? orderElement
                        .getOrder().getBudgetMargin() : 0;
                result.append(_("Budget-status") + "\n");
                result.append(_("Project margin: {0}% ({1})={2}", margin,
                        addCurrencySymbol(orderElement.getBudget()),
                        addCurrencySymbol(orderElement
                                .getWithMarginCalculatedBudget())));

                BigDecimal totalExpense = getTotalExpense(orderElement);

                result.append(_(". Already spent: {0}",
                        addCurrencySymbol(totalExpense)));

                return result.toString();
            }
        }

        @Override
        public ITaskFundamentalProperties adapt(final TaskElement taskElement) {
            return new TaskElementWrapper(scenario, taskElement);
        }

        @Override
        public List<DomainDependency<TaskElement>> getIncomingDependencies(
                TaskElement taskElement) {
            return toDomainDependencies(taskElement
                    .getDependenciesWithThisDestination());
        }

        @Override
        public List<DomainDependency<TaskElement>> getOutcomingDependencies(
                TaskElement taskElement) {
            return toDomainDependencies(taskElement
                    .getDependenciesWithThisOrigin());
        }

        private List<DomainDependency<TaskElement>> toDomainDependencies(
                Collection<? extends Dependency> dependencies) {
            List<DomainDependency<TaskElement>> result = new ArrayList<DomainDependency<TaskElement>>();
            for (Dependency dependency : dependencies) {
                result.add(DomainDependency.createDependency(
                        dependency.getOrigin(), dependency.getDestination(),
                        toGanntType(dependency.getType())));
            }
            return result;
        }

        private DependencyType toGanntType(Type type) {
            switch (type) {
            case END_START:
                return DependencyType.END_START;
            case START_END:
                return DependencyType.START_END;
            case START_START:
                return DependencyType.START_START;
            case END_END:
                return DependencyType.END_END;
            default:
                throw new RuntimeException(_("{0} not supported yet", type));
            }
        }

        private Type toDomainType(DependencyType type) {
            switch (type) {
            case END_START:
                return Type.END_START;
            case START_END:
                return Type.START_END;
            case START_START:
                return Type.START_START;
            case END_END:
                return Type.END_END;
            default:
                throw new RuntimeException(_("{0} not supported yet", type));
            }
        }

        @Override
        public void addDependency(DomainDependency<TaskElement> dependency) {
            TaskElement source = dependency.getSource();
            TaskElement destination = dependency.getDestination();
            Type domainType = toDomainType(dependency.getType());
            Dependency.create(source, destination, domainType);
        }

        @Override
        public boolean canAddDependency(DomainDependency<TaskElement> dependency) {
            return true;
        }

        @Override
        public void removeDependency(DomainDependency<TaskElement> dependency) {
            TaskElement source = dependency.getSource();
            Type type = toDomainType(dependency.getType());
            source.removeDependencyWithDestination(dependency.getDestination(),
                    type);
        }

        @Override
        public void doRemovalOf(TaskElement taskElement) {
            taskElement.detach();
            TaskGroup parent = taskElement.getParent();
            if (parent != null) {
                parent.remove(taskElement);
            }
        }

    }
}
TOP

Related Classes of org.libreplan.web.planner.TaskElementAdapter$Adapter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.