/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
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.orders.entities.Order;
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.LimitingResourceQueueDependency;
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.resources.entities.Resource;
import org.libreplan.web.common.Util;
import org.libreplan.web.limitingresources.LimitingResourcesPanel.IToolbarCommand;
import org.libreplan.web.planner.order.BankHolidaysMarker;
import org.libreplan.web.planner.taskedition.EditTaskController;
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.timetracker.TimeTracker;
import org.zkoss.ganttz.timetracker.zoom.SeveralModifiers;
import org.zkoss.ganttz.timetracker.zoom.ZoomLevel;
import org.zkoss.zk.ui.SuspendNotAllowedException;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Column;
import org.zkoss.zul.Grid;
import org.zkoss.zul.Hbox;
import org.zkoss.zul.Label;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Row;
import org.zkoss.zul.RowRenderer;
import org.zkoss.zul.SimpleListModel;
import org.zkoss.zul.Window;
import org.zkoss.zul.Rows;
/**
* Controller for limiting resources view.
* Queue-based Resources Planning page.
*
* @author Lorenzo Tilve Álvaro <ltilve@igalia.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class LimitingResourcesController extends GenericForwardComposer<org.zkoss.zk.ui.Component> {
@Autowired
private ILimitingResourceQueueModel limitingResourceQueueModel;
@Autowired
private IConfigurationDAO configurationDAO;
@Autowired
private IAdHocTransactionService transactionService;
private List<IToolbarCommand> commands = new ArrayList<>();
private LimitingResourcesPanel limitingResourcesPanel;
private TimeTracker timeTracker;
private Grid gridUnassignedLimitingResourceQueueElements;
private Checkbox cbSelectAll;
private Window manualAllocationWindow;
private Window editTaskWindow;
private final LimitingResourceQueueElementsRenderer limitingResourceQueueElementsRenderer =
new LimitingResourceQueueElementsRenderer();
public LimitingResourcesController() {
}
@Override
public void doAfterCompose(org.zkoss.zk.ui.Component comp) throws Exception {
super.doAfterCompose(comp);
}
public void add(IToolbarCommand... commands) {
Validate.noNullElements(commands);
this.commands.addAll(Arrays.asList(commands));
}
public void reload() {
transactionService.runOnReadOnlyTransaction(new IOnTransaction<Void>() {
@Override
public Void execute() {
reloadInTransaction();
return null;
}
private void reloadInTransaction() {
// FIXME: Temporary fix
// It seems the page was already rendered, so clear it all as it's going to be rendered again
self.getChildren().clear();
limitingResourceQueueModel.initGlobalView();
// Initialize interval
timeTracker = buildTimeTracker();
limitingResourcesPanel = buildLimitingResourcesPanel();
self.appendChild(limitingResourcesPanel);
limitingResourcesPanel.afterCompose();
cbSelectAll = (Checkbox) limitingResourcesPanel.getFellowIfAny("cbSelectAll");
initGridUnassignedLimitingResourceQueueElements();
initManualAllocationWindow();
initEditTaskWindow();
addCommands(limitingResourcesPanel);
}
});
}
private void initGridUnassignedLimitingResourceQueueElements() {
gridUnassignedLimitingResourceQueueElements =
(Grid) limitingResourcesPanel.getFellowIfAny("gridUnassignedLimitingResourceQueueElements");
gridUnassignedLimitingResourceQueueElements.setModel(
new SimpleListModel<>(getUnassignedLimitingResourceQueueElements()));
gridUnassignedLimitingResourceQueueElements.setRowRenderer(getLimitingResourceQueueElementsRenderer());
getEarlierStartingDateColumn().sort(true, true);
}
private Column getEarlierStartingDateColumn() {
return (Column) gridUnassignedLimitingResourceQueueElements.getColumns().getChildren().get(4);
}
private void initManualAllocationWindow() {
manualAllocationWindow = (Window) limitingResourcesPanel.getFellowIfAny("manualAllocationWindow");
ManualAllocationController manualAllocationController = getManualAllocationController();
manualAllocationController.setLimitingResourcesController(this);
manualAllocationController.setLimitingResourcesPanel(limitingResourcesPanel);
}
private ManualAllocationController getManualAllocationController() {
return (ManualAllocationController) manualAllocationWindow.getAttribute("manualAllocationController", true);
}
private void initEditTaskWindow() {
editTaskWindow = (Window) limitingResourcesPanel.getFellowIfAny("editTaskWindow");
}
public ILimitingResourceQueueModel getLimitingResourceQueueModel() {
return limitingResourceQueueModel;
}
private void addCommands(LimitingResourcesPanel limitingResourcesPanel) {
limitingResourcesPanel.add(commands.toArray(new IToolbarCommand[commands.size()]));
}
private TimeTracker buildTimeTracker() {
timeTracker = new TimeTracker(
limitingResourceQueueModel.getViewInterval(),
ZoomLevel.DETAIL_THREE,
SeveralModifiers.create(),
SeveralModifiers.create(BankHolidaysMarker.create(getDefaultCalendar())),
self);
return timeTracker;
}
private BaseCalendar getDefaultCalendar() {
return configurationDAO.getConfiguration().getDefaultCalendar();
}
private LimitingResourcesPanel buildLimitingResourcesPanel() {
return new LimitingResourcesPanel(this, timeTracker);
}
/**
* Returns unassigned {@link LimitingResourceQueueElement}.
*
* It's necessary to convert elements to a DTO that encapsulates properties
* such as task name or order name, since the only way of sorting by these
* fields is by having properties getTaskName or getOrderName on the elements returned.
*
* @return {@link List<LimitingResourceQueueElementDTO>}
*/
public List<LimitingResourceQueueElementDTO> getUnassignedLimitingResourceQueueElements() {
return limitingResourceQueueModel
.getUnassignedLimitingResourceQueueElements()
.stream()
.map(this::toLimitingResourceQueueElementDTO)
.collect(Collectors.toList());
}
private LimitingResourceQueueElementDTO toLimitingResourceQueueElementDTO(LimitingResourceQueueElement element) {
final Task task = element.getResourceAllocation().getTask();
final Order order = limitingResourceQueueModel.getOrderByTask(task);
return new LimitingResourceQueueElementDTO(
element,
order.getName(),
task.getName(),
element.getEarliestStartDateBecauseOfGantt());
}
public static String getResourceOrCriteria(ResourceAllocation<?> resourceAllocation) {
if ( resourceAllocation instanceof SpecificResourceAllocation ) {
final Resource resource = ((SpecificResourceAllocation) resourceAllocation).getResource();
return (resource != null) ? resource.getName() : "";
} else if ( resourceAllocation instanceof GenericResourceAllocation ) {
return Criterion.getCaptionFor((GenericResourceAllocation) resourceAllocation);
}
return StringUtils.EMPTY;
}
/**
* DTO for list of unassigned {@link LimitingResourceQueueElement}.
*
* Note: this class has a natural ordering that is inconsistent with equals.
*
* @author Diego Pino Garcia <dpino@igalia.com>
*
*/
public static class LimitingResourceQueueElementDTO implements Comparable<LimitingResourceQueueElementDTO> {
private LimitingResourceQueueElement original;
private String orderName;
private String taskName;
private String date;
private Integer hoursToAllocate;
private String resourceOrCriteria;
public LimitingResourceQueueElementDTO(LimitingResourceQueueElement element,
String orderName,
String taskName,
Date date) {
this.original = element;
this.orderName = orderName;
this.taskName = taskName;
this.date = Util.formatDate(date);
this.hoursToAllocate = element.getIntentedTotalHours();
this.resourceOrCriteria =
LimitingResourcesController.getResourceOrCriteria(element.getResourceAllocation());
}
public LimitingResourceQueueElement getOriginal() {
return original;
}
public String getOrderName() {
return orderName;
}
public String getTaskName() {
return taskName;
}
public String getDate() {
return date;
}
public Integer getHoursToAllocate() {
return (hoursToAllocate != null) ? hoursToAllocate : 0;
}
public String getResourceOrCriteria() {
return resourceOrCriteria;
}
@Override
public int compareTo(LimitingResourceQueueElementDTO dto) {
return getOriginal().getId().compareTo(dto.getOriginal().getId());
}
}
public void saveQueues() {
limitingResourceQueueModel.confirm();
notifyUserThatSavingIsDone();
}
private void notifyUserThatSavingIsDone() {
Messagebox.show(_("Scheduling saved"), _("Information"), Messagebox.OK, Messagebox.INFORMATION);
}
public void editResourceAllocation(LimitingResourceQueueElement oldElement) {
try {
Task task = oldElement.getTask();
EditTaskController editTaskController = getEditController();
editTaskController.showEditFormResourceAllocationFromLimitingResources(task);
// New resource allocation or resource allocation modified
if ( editTaskController.getStatus() == Messagebox.OK ) {
// Update resource allocation for element
LimitingResourceQueueElement newElement = copyFrom(oldElement, getQueueElementFrom(task));
// Replace old limiting resource with new one
LimitingResourceQueue oldQueue = oldElement.getLimitingResourceQueue();
List<LimitingResourceQueueElement> modified =
limitingResourceQueueModel.replaceLimitingResourceQueueElement(oldElement, newElement);
// Refresh modified queues
Set<LimitingResourceQueue> toRefreshQueues = new HashSet<>();
toRefreshQueues.addAll(LimitingResourceQueue.queuesOf(modified));
if ( oldQueue != null ) {
toRefreshQueues.add(oldQueue);
}
limitingResourcesPanel.refreshQueues(toRefreshQueues);
}
} catch (SuspendNotAllowedException e) {
e.printStackTrace();
}
}
private LimitingResourceQueueElement getQueueElementFrom(Task task) {
return task.getResourceAllocation().getLimitingResourceQueueElement();
}
/**
* Copies earliestStartDateBecauseOfGantt and dependencies from source to destination.
*
* @param source
* @param destination
* @return {@link LimitingResourceQueueElement}
*/
private LimitingResourceQueueElement copyFrom(LimitingResourceQueueElement source,
LimitingResourceQueueElement destination) {
destination.setEarlierStartDateBecauseOfGantt(source.getEarliestStartDateBecauseOfGantt());
for (LimitingResourceQueueDependency each : source.getDependenciesAsOrigin()) {
each.setOrigin(destination);
destination.add(each);
}
for (LimitingResourceQueueDependency each : source.getDependenciesAsDestiny()) {
each.setDestiny(destination);
destination.add(each);
}
return destination;
}
private EditTaskController getEditController() {
return (EditTaskController) editTaskWindow.getAttribute("editController", true);
}
public LimitingResourceQueueElementsRenderer getLimitingResourceQueueElementsRenderer() {
return limitingResourceQueueElementsRenderer;
}
private class LimitingResourceQueueElementsRenderer implements RowRenderer {
@Override
public void render(Row row, Object o, int i) throws Exception {
LimitingResourceQueueElementDTO element = (LimitingResourceQueueElementDTO) o;
row.setValue(o);
row.appendChild(automaticQueueing());
row.appendChild(label(element.getOrderName()));
row.appendChild(label(element.getTaskName()));
row.appendChild(label(element.getResourceOrCriteria()));
row.appendChild(label(element.getDate()));
row.appendChild(label(element.getHoursToAllocate().toString()));
row.appendChild(operations(element));
}
private Hbox operations(LimitingResourceQueueElementDTO element) {
Hbox hbox = new Hbox();
hbox.appendChild(editResourceAllocationButton(element));
hbox.appendChild(automaticButton(element));
hbox.appendChild(manualButton(element));
hbox.appendChild(removeButton(element));
return hbox;
}
private Button editResourceAllocationButton(final LimitingResourceQueueElementDTO element) {
Button result = new Button("", "/common/img/ico_editar1.png");
result.setHoverImage("/common/img/ico_editar.png");
result.setSclass("icono");
result.setTooltiptext(_("Edit queue-based resource element"));
result.addEventListener(Events.ON_CLICK, event -> {
LimitingResourceQueueElement queueElement = element.getOriginal();
editResourceAllocation(queueElement);
if ( queueElement.getLimitingResourceQueue() == null ) {
reloadUnassignedLimitingResourceQueueElements();
}
});
return result;
}
private Button manualButton(final LimitingResourceQueueElementDTO element) {
Button result = new Button();
result.setLabel(_("Manual"));
result.setClass("add-button");
result.setTooltiptext(_("Assign element to queue manually"));
result.addEventListener(Events.ON_CLICK, event -> showManualAllocationWindow(element.getOriginal()));
return result;
}
private Button removeButton(final LimitingResourceQueueElementDTO element) {
Button result = new Button("", "/common/img/ico_borrar1.png");
result.setHoverImage("/common/img/ico_borrar.png");
result.setSclass("icono");
result.setTooltiptext(_("Remove queue-based resource element"));
result.addEventListener(Events.ON_CLICK, event -> removeUnassignedLimitingResourceQueueElement(element));
return result;
}
private void removeUnassignedLimitingResourceQueueElement(LimitingResourceQueueElementDTO dto) {
LimitingResourceQueueElement element = dto.getOriginal();
limitingResourceQueueModel.removeUnassignedLimitingResourceQueueElement(element);
reloadUnassignedLimitingResourceQueueElements();
}
private Button automaticButton(final LimitingResourceQueueElementDTO element) {
Button result = new Button();
result.setLabel(_("Automatic"));
result.setClass("add-button");
result.setTooltiptext(_("Assign element to queue automatically"));
result.setStyle("margin-right: 5px");
result.addEventListener(Events.ON_CLICK, event -> assignLimitingResourceQueueElement(element));
return result;
}
private void assignLimitingResourceQueueElement(LimitingResourceQueueElementDTO dto) {
List<LimitingResourceQueueElement> inserted =
limitingResourceQueueModel.assignLimitingResourceQueueElement(dto.getOriginal());
if ( inserted.isEmpty() ) {
showErrorMessage(
_("Cannot allocate selected element. There is not any queue " +
"that matches resource allocation criteria at any interval of time"));
return;
}
limitingResourcesPanel.refreshQueues(LimitingResourceQueue.queuesOf(inserted));
reloadUnassignedLimitingResourceQueueElements();
}
private Checkbox automaticQueueing() {
Checkbox result = new Checkbox();
result.setTooltiptext(_("Select for automatic queuing"));
result.setStyle("margin-left: 2px");
return result;
}
private Label label(String value) {
return new Label(value);
}
}
public List<LimitingResourceQueue> getLimitingResourceQueues() {
return limitingResourceQueueModel.getLimitingResourceQueues();
}
public void unschedule(QueueTask task) {
LimitingResourceQueueElement queueElement = task.getLimitingResourceQueueElement();
LimitingResourceQueue queue = queueElement.getLimitingResourceQueue();
limitingResourceQueueModel.unschedule(queueElement);
limitingResourcesPanel.refreshQueue(queue);
reloadUnassignedLimitingResourceQueueElements();
}
public boolean moveTask(LimitingResourceQueueElement element) {
showManualAllocationWindow(element);
return getManualAllocationWindowStatus() == Messagebox.OK;
}
private void showManualAllocationWindow(LimitingResourceQueueElement element) {
getManualAllocationController().show(element);
}
public int getManualAllocationWindowStatus() {
Integer status = getManualAllocationController().getStatus();
return (status != null) ? status : -1;
}
public void reloadUnassignedLimitingResourceQueueElements() {
gridUnassignedLimitingResourceQueueElements.setModel(
new SimpleListModel<>(getUnassignedLimitingResourceQueueElements()));
}
public void selectedAllUnassignedQueueElements() {
final boolean value = cbSelectAll.isChecked();
final Rows rows = gridUnassignedLimitingResourceQueueElements.getRows();
for (Object each: rows.getChildren()) {
final Row row = (Row) each;
Checkbox cbAutoQueueing = getAutoQueueing(row);
cbAutoQueueing.setChecked(value);
}
}
@SuppressWarnings("unchecked")
private Checkbox getAutoQueueing(Row row) {
return (Checkbox) row.getChildren().get(0);
}
public void assignAllSelectedElements() {
List<LimitingResourceQueueElement> elements = getAllSelectedQueueElements();
if ( !elements.isEmpty() ) {
Set<LimitingResourceQueueElement> inserted =
limitingResourceQueueModel.assignLimitingResourceQueueElements(elements);
clearSelectAllCheckbox();
if ( inserted.isEmpty() ) {
showErrorMessage(_("Cannot allocate selected element. There is not any queue " +
"that matches resource allocation criteria at any interval of time"));
return;
}
limitingResourcesPanel.refreshQueues(LimitingResourceQueue.queuesOf(inserted));
reloadUnassignedLimitingResourceQueueElements();
}
}
private void clearSelectAllCheckbox() {
cbSelectAll.setChecked(false);
}
private List<LimitingResourceQueueElement> getAllSelectedQueueElements() {
List<LimitingResourceQueueElement> result = new ArrayList<>();
final Rows rows = gridUnassignedLimitingResourceQueueElements.getRows();
for (Object each : rows.getChildren()) {
final Row row = (Row) each;
Checkbox cbAutoQueueing = getAutoQueueing(row);
if ( cbAutoQueueing.isChecked() ) {
LimitingResourceQueueElementDTO dto = row.getValue();
result.add(dto.getOriginal());
}
}
return result;
}
private void showErrorMessage(String error) {
Messagebox.show(error, _("Error"), Messagebox.OK, Messagebox.ERROR);
}
}