Package org.libreplan.web.limitingresources

Source Code of org.libreplan.web.limitingresources.QueueComponent

/*
* 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.limitingresources;

import static org.libreplan.web.I18nHelper._;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;

import org.joda.time.Duration;
import org.joda.time.LocalDate;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.planner.entities.DayAssignment;
import org.libreplan.business.planner.entities.GenericResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.SpecificResourceAllocation;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.limiting.entities.DateAndHour;
import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.LimitingResourceQueue;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;
import org.zkoss.ganttz.DatesMapperOnInterval;
import org.zkoss.ganttz.IDatesMapper;
import org.zkoss.ganttz.timetracker.TimeTracker;
import org.zkoss.ganttz.timetracker.zoom.IZoomLevelChangedListener;
import org.zkoss.ganttz.timetracker.zoom.ZoomLevel;
import org.zkoss.ganttz.util.MenuBuilder;
import org.zkoss.ganttz.util.MenuBuilder.ItemAction;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.ext.AfterCompose;
import org.zkoss.zk.ui.sys.ContentRenderer;
import org.zkoss.zul.Div;
import org.zkoss.zul.impl.XulElement;


/**
* This class wraps ResourceLoad data inside an specific HTML Div component.
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
*/
public class QueueComponent extends XulElement implements
        AfterCompose {

    private static final int DEADLINE_MARK_HALF_WIDTH = 5;

    public static QueueComponent create(
            QueueListComponent queueListComponent,
            TimeTracker timeTracker,
            LimitingResourceQueue limitingResourceQueue) {

        return new QueueComponent(queueListComponent, timeTracker,
                limitingResourceQueue);
    }

    private final QueueListComponent queueListComponent;

    private final TimeTracker timeTracker;

    private transient IZoomLevelChangedListener zoomChangedListener;

    private LimitingResourceQueue limitingResourceQueue;

    private List<QueueTask> queueTasks = new ArrayList<QueueTask>();

    private QueueComponent(
            final QueueListComponent queueListComponent,
            final TimeTracker timeTracker,
            final LimitingResourceQueue limitingResourceQueue) {

        this.queueListComponent = queueListComponent;
        this.limitingResourceQueue = limitingResourceQueue;
        this.timeTracker = timeTracker;

        createChildren(limitingResourceQueue, timeTracker.getMapper());
        zoomChangedListener = new IZoomLevelChangedListener() {

            @Override
            public void zoomLevelChanged(ZoomLevel detailLevel) {
                getChildren().clear();
                createChildren(limitingResourceQueue, timeTracker.getMapper());
            }
        };
        this.timeTracker.addZoomListener(zoomChangedListener);
    }

    @Override
    public void afterCompose() {
        appendContextMenus();
    }

    public List<QueueTask> getQueueTasks() {
        return queueTasks;
    }

    public void setLimitingResourceQueue(LimitingResourceQueue limitingResourceQueue) {
        this.limitingResourceQueue = limitingResourceQueue;
    }

    private void createChildren(LimitingResourceQueue limitingResourceQueue,
            IDatesMapper mapper) {
        List<QueueTask> queueTasks = createQueueTasks(mapper,
                limitingResourceQueue.getLimitingResourceQueueElements());
        appendQueueTasks(queueTasks);
    }

    public QueueListComponent getQueueListComponent() {
        return queueListComponent;
    }

    public LimitingResourcesPanel getLimitingResourcesPanel() {
        return queueListComponent.getLimitingResourcePanel();
    }

    public void invalidate() {
        removeChildren();
        appendQueueElements(limitingResourceQueue.getLimitingResourceQueueElements());
    }

    private void removeChildren() {
        for (QueueTask each: queueTasks) {
            removeChild(each);
        }
        queueTasks.clear();
    }

    private void appendQueueTasks(List<QueueTask> queueTasks) {
        for (QueueTask each: queueTasks) {
            appendQueueTask(each);
        }
    }

    private void appendQueueTask(QueueTask queueTask) {
        queueTasks.add(queueTask);
        appendChild(queueTask);
    }

    private void removeQueueTask(QueueTask queueTask) {
        queueTasks.remove(queueTask);
        removeChild(queueTask);
    }

    private List<QueueTask> createQueueTasks(IDatesMapper datesMapper,
            Set<LimitingResourceQueueElement> list) {

        List<QueueTask> result = new ArrayList<QueueTask>();

        org.zkoss.ganttz.util.Interval interval = null;
        if (timeTracker.getFilter() != null) {
            timeTracker.getFilter().resetInterval();
            interval = timeTracker.getFilter().getCurrentPaginationInterval();
        }
        for (LimitingResourceQueueElement each : list) {
            if (interval != null) {
                if (each.getEndDate().toDateMidnight()
                        .isAfter(interval.getStart().toDateMidnight())
                        && each.getStartDate().toDateMidnight()
                                .isBefore(interval.getFinish().toDateMidnight())) {
                    result.add(createQueueTask(datesMapper, each));
                }
            } else {
                result.add(createQueueTask(datesMapper, each));
            }
        }
        return result;
    }

    private static QueueTask createQueueTask(IDatesMapper datesMapper, LimitingResourceQueueElement element) {
        validateQueueElement(element);
        return createDivForElement(datesMapper, element);
    }

    private static OrderElement getRootOrder(Task task) {
        OrderElement order = task.getOrderElement();
        while (order.getParent() != null) {
            order = order.getParent();
        }
        return order;
    }

    private static String createTooltiptext(LimitingResourceQueueElement element) {
        final Task task = element.getResourceAllocation().getTask();
        final OrderElement order = getRootOrder(task);

        StringBuilder result = new StringBuilder();
        result.append(_("Project: {0}", order.getName()) + " ");
        result.append(_("Task: {0}", task.getName()) + " ");
        result.append(_("Completed: {0}%", element.getAdvancePercentage().multiply(new BigDecimal(100))) + " ");

        final ResourceAllocation<?> resourceAllocation = element.getResourceAllocation();
        if (resourceAllocation instanceof SpecificResourceAllocation) {
            final SpecificResourceAllocation specific = (SpecificResourceAllocation) resourceAllocation;
            result.append(_("Resource: {0}", specific.getResource().getName()) + " ");
        } else if (resourceAllocation instanceof GenericResourceAllocation) {
            final GenericResourceAllocation generic = (GenericResourceAllocation) resourceAllocation;
            result.append(_("Criteria: {0}", Criterion.getCaptionFor(generic.getCriterions())) + " ");
        }
        result.append(_("Allocation: [{0},{1}]", element.getStartDate()
                .toString(), element.getEndDate()));

        return result.toString();
    }

    /**
     * Returns end date considering % of task completion
     *
     * @param element
     * @return
     */
    private static DateAndHour getAdvanceEndDate(LimitingResourceQueueElement element) {
        int hoursWorked = 0;
        final List<? extends DayAssignment> dayAssignments = element.getDayAssignments();
        if (element.hasDayAssignments()) {
            final int estimatedWorkedHours = estimatedWorkedHours(element.getIntentedTotalHours(), element.getAdvancePercentage());

            for (DayAssignment each: dayAssignments) {
                hoursWorked += each.getHours();
                if (hoursWorked >= estimatedWorkedHours) {
                    int hourSlot = each.getHours() - (hoursWorked - estimatedWorkedHours);
                    return new DateAndHour(each.getDay(), hourSlot);
                }
            }
        }
        if (hoursWorked != 0) {
            DayAssignment lastDayAssignment = dayAssignments.get(dayAssignments.size() - 1);
            return new DateAndHour(lastDayAssignment.getDay(), lastDayAssignment.getHours());
        }
        return null;
    }

    private static int estimatedWorkedHours(Integer totalHours, BigDecimal percentageWorked) {
        return (totalHours != null && percentageWorked != null) ? percentageWorked.multiply(
                new BigDecimal(totalHours)).intValue() : 0;
    }

    private static QueueTask createDivForElement(IDatesMapper datesMapper,
            LimitingResourceQueueElement queueElement) {

        final Task task = queueElement.getResourceAllocation().getTask();
        final OrderElement order = getRootOrder(task);

        QueueTask result = new QueueTask(queueElement);
        String cssClass = "queue-element";
        result.setTooltiptext(createTooltiptext(queueElement));

        int startPixels = getStartPixels(datesMapper, queueElement);
        result.setLeft(forCSS(startPixels));
        if (startPixels < 0) {
            cssClass += " truncated-start ";
        }

        int taskWidth = getWidthPixels(datesMapper, queueElement);
        if ((startPixels + taskWidth) > datesMapper.getHorizontalSize()) {
            taskWidth = datesMapper.getHorizontalSize() - startPixels;
            cssClass += " truncated-end ";
        } else {
            result.appendChild(generateNonWorkableShade(datesMapper,
                    queueElement));
        }
        result.setWidth(forCSS(taskWidth));

        LocalDate deadlineDate = task.getDeadline();
        boolean isOrderDeadline = false;
        if (deadlineDate == null) {
            Date orderDate = order.getDeadline();
            if (orderDate != null) {
                deadlineDate = LocalDate.fromDateFields(orderDate);
                isOrderDeadline = true;
            }
        }
        if (deadlineDate != null) {
            int deadlinePosition = getDeadlinePixels(datesMapper, deadlineDate);
            if (deadlinePosition < datesMapper.getHorizontalSize()) {
                Div deadline = new Div();
                deadline.setSclass(isOrderDeadline ? "deadline order-deadline"
                        : "deadline");
                deadline
                        .setLeft((deadlinePosition - startPixels - DEADLINE_MARK_HALF_WIDTH)
                                + "px");
                result.appendChild(deadline);
                result.appendChild(generateNonWorkableShade(datesMapper,
                        queueElement));
            }
            if (deadlineDate.isBefore(queueElement.getEndDate())) {
                cssClass += " unmet-deadline ";
            }
        }

        result.setClass(cssClass);
        result.appendChild(generateCompletionShade(datesMapper, queueElement));
        Component progressBar = generateProgressBar(datesMapper, queueElement,
                task, startPixels);
        if (progressBar != null) {
            result.appendChild(progressBar);
        }
        return result;
    }

    private static Component generateProgressBar(IDatesMapper datesMapper,
            LimitingResourceQueueElement queueElement, Task task,
            int startPixels) {
        DateAndHour advancementEndDate = getAdvanceEndDate(queueElement);
        if (advancementEndDate == null) {
            return null;
        }
        Duration durationBetween = new Duration(queueElement.getStartTime()
                .toDateTime().getMillis(), advancementEndDate.toDateTime().getMillis());
        Div progressBar = new Div();
        if (!queueElement.getStartDate().isEqual(advancementEndDate.getDate())) {
            progressBar.setWidth(datesMapper.toPixels(durationBetween) + "px");
            progressBar.setSclass("queue-progress-bar");
        }
        return progressBar;
    }

    private static Div generateNonWorkableShade(IDatesMapper datesMapper,
            LimitingResourceQueueElement queueElement) {

        int workableHours = queueElement.getLimitingResourceQueue()
                .getResource().getCalendar()
                .getCapacityOn(PartialDay.wholeDay(queueElement.getEndDate()))
                .roundToHours();

        int shadeWidth = Long.valueOf(
                (24 - workableHours)
                        * DatesMapperOnInterval.MILISECONDS_PER_HOUR
                        / datesMapper.getMilisecondsPerPixel()).intValue();

        int shadeLeft = Long.valueOf(
                (workableHours - queueElement.getEndHour())
                        * DatesMapperOnInterval.MILISECONDS_PER_HOUR
                        / datesMapper.getMilisecondsPerPixel()).intValue()
                + shadeWidth;
        ;

        Div notWorkableHoursShade = new Div();
        notWorkableHoursShade
                .setTooltiptext(_("Workable capacity for this period ")
                        + workableHours + _(" hours"));

        notWorkableHoursShade.setContext("");
        notWorkableHoursShade.setSclass("not-workable-hours");

        notWorkableHoursShade.setStyle("left: " + shadeLeft + "px; width: "
                + shadeWidth + "px;");
        return notWorkableHoursShade;
    }

    private static Div generateCompletionShade(IDatesMapper datesMapper,
            LimitingResourceQueueElement queueElement) {

        int workableHours = queueElement.getLimitingResourceQueue()
                .getResource().getCalendar()
                .getCapacityOn(PartialDay.wholeDay(queueElement.getEndDate()))
                .roundToHours();

        int shadeWidth = new Long((24 - workableHours)
                * DatesMapperOnInterval.MILISECONDS_PER_HOUR
                / datesMapper.getMilisecondsPerPixel()).intValue();

        int shadeLeft = new Long((workableHours - queueElement.getEndHour())
                * DatesMapperOnInterval.MILISECONDS_PER_HOUR
                / datesMapper.getMilisecondsPerPixel()).intValue()
                + shadeWidth;

        Div notWorkableHoursShade = new Div();
        notWorkableHoursShade.setContext("");
        notWorkableHoursShade.setSclass("limiting-completion");

        notWorkableHoursShade.setStyle("left: " + shadeLeft + "px; width: "
                + shadeWidth + "px;");
        return notWorkableHoursShade;
    }

    private static int getWidthPixels(IDatesMapper datesMapper,
            LimitingResourceQueueElement queueElement) {
        return datesMapper.toPixels(queueElement.getLengthBetween());
    }

    private static int getDeadlinePixels(IDatesMapper datesMapper,
            LocalDate deadlineDate) {
        // Deadline date is considered inclusively
        return datesMapper.toPixelsAbsolute(deadlineDate.plusDays(1)
                .toDateMidnight().getMillis());
    }

    private static String forCSS(int pixels) {
        return String.format("%dpx", pixels);
    }

    private static int getStartPixels(IDatesMapper datesMapper,
            LimitingResourceQueueElement queueElement) {
        return datesMapper.toPixelsAbsolute((queueElement.getStartDate()
                .toDateMidnight().getMillis() + queueElement.getStartHour()
                * DatesMapperOnInterval.MILISECONDS_PER_HOUR));
    }

    public void appendQueueElements(SortedSet<LimitingResourceQueueElement> elements) {
        for (LimitingResourceQueueElement each : elements) {
            appendQueueElement(each);
        }
    }

    public void appendQueueElement(LimitingResourceQueueElement element) {
        QueueTask queueTask = createQueueTask(element);
        appendQueueTask(queueTask);
        appendMenu(queueTask);
        addDependenciesInPanel(element);
    }

    public void removeQueueElement(LimitingResourceQueueElement element) {
        QueueTask queueTask = findQueueTaskByElement(element);
        if (queueTask != null) {
            removeQueueTask(queueTask);
        }
    }

    private QueueTask findQueueTaskByElement(LimitingResourceQueueElement element) {
        for (QueueTask each: queueTasks) {
            if (each.getLimitingResourceQueueElement().getId().equals(element.getId())) {
                return each;
            }
        }
        return null;
    }

    private QueueTask createQueueTask(LimitingResourceQueueElement element) {
        validateQueueElement(element);
        return createDivForElement(timeTracker.getMapper(), element);
    }

    private void addDependenciesInPanel(LimitingResourceQueueElement element) {
        getLimitingResourcesPanel().addDependenciesFor(element);
    }

    public String getResourceName() {
        return limitingResourceQueue.getResource().getName();
    }

    private static void validateQueueElement(
            LimitingResourceQueueElement queueElement) {
        if ((queueElement.getStartDate() == null)
                || (queueElement.getEndDate() == null)) {
            throw new ValidationException(_("Invalid queue element"));
        }
    }

    private void appendMenu(QueueTask divElement) {
        if (divElement.getPage() != null) {
            MenuBuilder<QueueTask> menuBuilder = MenuBuilder.on(divElement
                    .getPage(), divElement);

            menuBuilder.item(_("Edit"), "/common/img/ico_editar.png",
                    new ItemAction<QueueTask>() {
                        @Override
                        public void onEvent(QueueTask choosen, Event event) {
                            editResourceAllocation(choosen);
                        }
                    });
            menuBuilder.item(_("Unassign"), "/common/img/ico_borrar.png",
                    new ItemAction<QueueTask>() {
                        @Override
                        public void onEvent(QueueTask choosen, Event event) {
                            unnasign(choosen);
                        }
                    });
            menuBuilder.item(_("Move"), "",
                    new ItemAction<QueueTask>() {
                        @Override
                        public void onEvent(QueueTask choosen, Event event) {
                            moveQueueTask(choosen);
                        }
                    });
            divElement.setContext(menuBuilder.createWithoutSettingContext());
        }
    }

    private void editResourceAllocation(QueueTask queueTask) {
        getLimitingResourcesPanel().editResourceAllocation(queueTask);
    }

    private void moveQueueTask(QueueTask queueTask) {
        getLimitingResourcesPanel().moveQueueTask(queueTask);
    }

    private void unnasign(QueueTask choosen) {
        getLimitingResourcesPanel().unschedule(choosen);
    }

    private void appendContextMenus() {
        for (QueueTask each : queueTasks) {
            appendMenu(each);
        }
    }

    public void renderProperties(ContentRenderer renderer) throws IOException{
        super.renderProperties(renderer);

        render(renderer, "_resourceName", getResourceName());
    }

}
TOP

Related Classes of org.libreplan.web.limitingresources.QueueComponent

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.