Java tutorial
/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundacin para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolxico de Galicia * Copyright (C) 2010-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.taskedition; import static org.libreplan.web.I18nHelper._; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import org.joda.time.LocalDate; import org.libreplan.business.advance.bootstrap.PredefinedAdvancedTypes; import org.libreplan.business.advance.entities.AdvanceType; import org.libreplan.business.advance.entities.DirectAdvanceAssignment; import org.libreplan.business.advance.exceptions.DuplicateAdvanceAssignmentForOrderElementException; import org.libreplan.business.email.entities.EmailTemplateEnum; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.planner.entities.ITaskPositionConstrained; import org.libreplan.business.planner.entities.PositionConstraintType; import org.libreplan.business.planner.entities.Task; import org.libreplan.business.planner.entities.TaskElement; import org.libreplan.business.planner.entities.TaskPositionConstraint; import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.Worker; import org.libreplan.business.scenarios.IScenarioManager; import org.libreplan.business.users.entities.User; import org.libreplan.business.users.entities.UserRole; import org.libreplan.business.workingday.IntraDayDate; import org.libreplan.web.I18nHelper; import org.libreplan.web.common.Util; import org.libreplan.web.email.IEmailNotificationModel; import org.libreplan.web.planner.allocation.AllocationResult; import org.libreplan.web.planner.order.SaveCommandBuilder; import org.libreplan.web.resources.worker.IWorkerModel; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.dao.DataIntegrityViolationException; import org.zkoss.ganttz.TaskEditFormComposer; import org.zkoss.ganttz.TaskEditFormComposer.TaskDTO; import org.zkoss.ganttz.data.TaskContainer; import org.zkoss.ganttz.extensions.IContextWithPlannerTask; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.event.SelectEvent; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zkplus.spring.SpringUtil; import org.zkoss.zul.Comboitem; import org.zkoss.zul.Decimalbox; import org.zkoss.zul.Intbox; import org.zkoss.zul.Listbox; import org.zkoss.zul.Listcell; import org.zkoss.zul.Listitem; import org.zkoss.zul.Messagebox; import org.zkoss.zul.Combobox; import org.zkoss.zul.Datebox; import org.zkoss.zul.Row; import org.zkoss.zul.Tabpanel; /** * Controller for edit {@link Task} popup. * * @author Manuel Rego Casasnovas <mrego@igalia.com> * @author Vova Perebykivskyi <vova@libreplan-enterprise.com> */ @org.springframework.stereotype.Component("taskPropertiesController") @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class TaskPropertiesController extends GenericForwardComposer<Component> { private final String WARNING = "Warning"; private IScenarioManager scenarioManager; private TaskEditFormComposer taskEditFormComposer = new TaskEditFormComposer(); private EditTaskController editTaskController; private TaskElement currentTaskElement; private Tabpanel tabpanel; private Intbox hours; private Intbox duration; private Decimalbox budget; private Datebox startDateBox; private Datebox endDateBox; private Datebox deadLineDateBox; private Combobox startConstraintTypes; private Datebox startConstraintDate; private Row startConstraint; private IContextWithPlannerTask<TaskElement> currentContext; private Row resourceAllocationType; private Listbox lbResourceAllocationType; private ResourceAllocationTypeEnum originalState; private boolean disabledConstraintsAndAllocations = false; public static AllocationResult allocationResult; private IEmailNotificationModel emailNotificationModel; private IWorkerModel workerModel; private boolean isResourcesAdded = false; private List<Resource> listToDelete = new ArrayList<>(); private List<Resource> listToAdd = new ArrayList<>(); public TaskPropertiesController() { if (emailNotificationModel == null) { emailNotificationModel = (IEmailNotificationModel) SpringUtil.getBean("emailNotificationModel"); } if (workerModel == null) { workerModel = (IWorkerModel) SpringUtil.getBean("workerModel"); } if (scenarioManager == null) { scenarioManager = (IScenarioManager) SpringUtil.getBean("scenarioManager"); } } public void init(final EditTaskController editTaskController, IContextWithPlannerTask<TaskElement> context, TaskElement taskElement) { this.editTaskController = editTaskController; this.currentContext = context; this.currentTaskElement = taskElement; Order order; if (context != null) { order = findOrderIn(context); } else { order = taskElement.getOrderElement().getOrder(); } setItemsStartConstraintTypesCombo(order); originalState = getResourceAllocationType(currentTaskElement); setOldState(originalState); disabledConstraintsAndAllocations = currentTaskElement.isSubcontractedAndWasAlreadySent() || currentTaskElement.isLimitingAndHasDayAssignments() || currentTaskElement.isUpdatedFromTimesheets(); if (!disabledConstraintsAndAllocations && (currentTaskElement.isTask())) { disabledConstraintsAndAllocations = ((Task) currentTaskElement).isManualAnyAllocation(); } startConstraintTypes.setDisabled(disabledConstraintsAndAllocations); startConstraintDate.setDisabled(disabledConstraintsAndAllocations); lbResourceAllocationType.setDisabled(disabledConstraintsAndAllocations); deadLineDateBox.setDisabled(currentTaskElement.isSubcontracted()); if (context != null) { taskEditFormComposer.init(context.getTask()); } updateComponentValuesForTask(); } private void setItemsStartConstraintTypesCombo(Order order) { startConstraintTypes.getChildren().clear(); for (PositionConstraintType type : PositionConstraintType.values()) { boolean firstCondition = type != PositionConstraintType.AS_LATE_AS_POSSIBLE && type != PositionConstraintType.AS_SOON_AS_POSSIBLE; boolean secondCondition = type == PositionConstraintType.AS_LATE_AS_POSSIBLE && order.getDeadline() != null; boolean thirdCondition = type == PositionConstraintType.AS_SOON_AS_POSSIBLE && order.getInitDate() != null; if (firstCondition || secondCondition || thirdCondition) { Comboitem comboitem = new Comboitem(_(type.getName())); comboitem.setValue(type); startConstraintTypes.appendChild(comboitem); } } } private Order findOrderIn(IContextWithPlannerTask<TaskElement> context) { return context.getMapper().findAssociatedDomainObject(findTopMostTask(context)).getParent() .getOrderElement().getOrder(); } private OrderElement findOrderElementIn(IContextWithPlannerTask<TaskElement> context) { return context.getMapper().findAssociatedDomainObject(findTopMostTask(context)).getOrderElement(); } private org.zkoss.ganttz.data.Task findTopMostTask(IContextWithPlannerTask<TaskElement> context) { List<? extends TaskContainer> parents = context.getMapper().getParents(context.getTask()); return parents.isEmpty() ? context.getTask() : parents.get(parents.size() - 1); } private void setOldState(ResourceAllocationTypeEnum state) { lbResourceAllocationType.setAttribute("oldState", state, true); } private ResourceAllocationTypeEnum getOldState() { return (ResourceAllocationTypeEnum) lbResourceAllocationType.getAttribute("oldState", true); } private void setResourceAllocationType(Listbox listbox, ResourceAllocationTypeEnum value) { setResourceAllocationType(listbox, value.toString()); } private void setResourceAllocationType(Listbox listbox, String label) { for (Component component : listbox.getChildren()) { Listitem item = (Listitem) component; Listcell cell = (Listcell) item.getFirstChild(); if (cell.getLabel() != null && cell.getLabel().equals(label)) { item.setSelected(true); } } } private void updateComponentValuesForTask() { if (currentTaskElement instanceof Task) { Task task = (Task) currentTaskElement; showDurationRow(task); showStartConstraintRow(task); showResourceAllocationTypeRow(); } else { hideDurationRow(); if (currentTaskElement instanceof ITaskPositionConstrained) { showStartConstraintRow((ITaskPositionConstrained) currentTaskElement); } else { hideStartConstraintRow(); } hideResourceAllocationTypeRow(); } hours.setValue(currentTaskElement.getWorkHours()); budget.setValue(currentTaskElement.getBudget()); Util.reloadBindings(tabpanel); } private void hideResourceAllocationTypeRow() { resourceAllocationType.setVisible(false); } private void showResourceAllocationTypeRow() { resourceAllocationType.setVisible(true); } private void hideStartConstraintRow() { startConstraint.setVisible(false); } private void showStartConstraintRow(ITaskPositionConstrained task) { startConstraint.setVisible(true); PositionConstraintType type = task.getPositionConstraint().getConstraintType(); startConstraintTypes.setSelectedItem(findComboWithType(type)); updateStartConstraint(type); } private Comboitem findComboWithType(PositionConstraintType type) { for (Object component : startConstraintTypes.getChildren()) { if (component instanceof Comboitem) { Comboitem item = (Comboitem) component; if ((item.getValue()) == type) { return item; } } } return null; } private void constraintTypeChosen(PositionConstraintType constraint) { startConstraintDate.setVisible(constraint.isAssociatedDateRequired()); updateStartConstraint(constraint); } private void updateStartConstraint(PositionConstraintType type) { TaskPositionConstraint taskStartConstraint = currentTaskElementAsTaskLeafConstraint() .getPositionConstraint(); startConstraintDate.setVisible(type.isAssociatedDateRequired()); if (taskStartConstraint.getConstraintDateAsDate() != null) { startConstraintDate.setValue(taskStartConstraint.getConstraintDateAsDate()); } } private boolean saveConstraintChanges() { TaskPositionConstraint taskConstraint = currentTaskElementAsTaskLeafConstraint().getPositionConstraint(); PositionConstraintType type = startConstraintTypes.getSelectedItem().getValue(); IntraDayDate inputDate = type.isAssociatedDateRequired() ? IntraDayDate.startOfDay(LocalDate.fromDateFields(startConstraintDate.getValue())) : null; if (taskConstraint.isValid(type, inputDate)) { taskConstraint.update(type, inputDate); /* * At this point we could call currentContext.recalculatePosition(currentTaskElement) * to trigger the scheduling algorithm, but we don't do it because * the ResourceAllocationController, which is attached to the other * tab of the same window, will do it anyway. */ return true; } else { return false; } } private ITaskPositionConstrained currentTaskElementAsTaskLeafConstraint() { return (ITaskPositionConstrained) currentTaskElement; } private void hideDurationRow() { hours.getFellow("durationRow").setVisible(false); } private void showDurationRow(Task task) { hours.getFellow("durationRow").setVisible(true); duration.setValue(task.getWorkableDays()); } @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); tabpanel = (Tabpanel) comp; taskEditFormComposer.doAfterCompose(comp); startConstraintTypes.addEventListener(Events.ON_SELECT, event -> { PositionConstraintType constraint = startConstraintTypes.getSelectedItem().getValue(); constraintTypeChosen(constraint); }); lbResourceAllocationType.addEventListener(Events.ON_SELECT, new EventListener() { @Override public void onEvent(Event event) { SelectEvent se = (SelectEvent) event; final ResourceAllocationTypeEnum oldState = getOldState(); ResourceAllocationTypeEnum newState = getSelectedValue(new ArrayList(se.getSelectedItems())); if (thereIsTransition(newState)) { if (isConsolidatedTask()) { restoreOldState(); editTaskController.showNonPermitChangeResourceAllocationType(); } else { if (newState.equals(ResourceAllocationTypeEnum.SUBCONTRACT) && !checkCompatibleAllocation()) { restoreOldState(); Messagebox.show(_("This resource allocation type is incompatible. The task has " + "an associated order element which has a progress that is of type subcontractor. "), _("Error"), Messagebox.OK, Messagebox.ERROR); } else { changeResourceAllocationType(oldState, newState); editTaskController.selectAssignmentTab(lbResourceAllocationType.getSelectedIndex() + 1); } } } if (oldState == null) { setOldState(newState); } } private ResourceAllocationTypeEnum getSelectedValue(List<Listitem> selectedItems) { final Listitem item = selectedItems.get(0); return ResourceAllocationTypeEnum.asEnum(((Listcell) item.getChildren().get(0)).getLabel()); } private void restoreOldState() { Util.reloadBindings(lbResourceAllocationType); } }); } private boolean checkCompatibleAllocation() { OrderElement orderElement; AdvanceType advanceType = PredefinedAdvancedTypes.SUBCONTRACTOR.getType(); if (this.currentContext != null) { orderElement = findOrderElementIn(this.currentContext); } else { orderElement = this.currentTaskElement.getOrderElement(); } if (orderElement.getAdvanceAssignmentByType(advanceType) != null) { return false; } try { DirectAdvanceAssignment newAdvanceAssignment = DirectAdvanceAssignment.create(); newAdvanceAssignment.setAdvanceType(advanceType); orderElement.checkAncestorsNoOtherAssignmentWithSameAdvanceType(orderElement.getParent(), newAdvanceAssignment); } catch (DuplicateAdvanceAssignmentForOrderElementException e) { return false; } return true; } private boolean thereIsTransition(ResourceAllocationTypeEnum newState) { return getOldState() != null && !getOldState().equals(newState); } public TaskDTO getGanttTaskDTO() { return taskEditFormComposer == null ? null : taskEditFormComposer.getTaskDTO(); } public void accept() { if (!isResourcesAdded) { listToAdd.clear(); } SaveCommandBuilder.taskPropertiesController = getObject(); boolean ok = true; if (currentTaskElement instanceof ITaskPositionConstrained) { ok = saveConstraintChanges(); } if (ok) { if (disabledConstraintsAndAllocations) { taskEditFormComposer.accept(); } else { taskEditFormComposer.acceptWithoutCopyingDates(); } } } public void cancel() { taskEditFormComposer.cancel(); } /** * Enum for showing type of resource assignation option list. * * @author Diego Pino Garcia <dpino@igalia.com> */ enum ResourceAllocationTypeEnum { NON_LIMITING_RESOURCES(_("Normal resource assignment")), LIMITING_RESOURCES( _("Queue-based resource assignation")), SUBCONTRACT(_("Subcontract")); private String option; private static final List<ResourceAllocationTypeEnum> nonMasterOptionList = new ArrayList<ResourceAllocationTypeEnum>() { { add(NON_LIMITING_RESOURCES); add(SUBCONTRACT); } }; ResourceAllocationTypeEnum(String option) { this.option = option; } /** * Forces to mark the string as needing translation. */ private static String _(String string) { return string; } @Override public String toString() { return I18nHelper._(option); } public static List<ResourceAllocationTypeEnum> getOptionList() { return Arrays.asList(values()); } public static List<ResourceAllocationTypeEnum> getOptionListForNonMasterBranch() { return nonMasterOptionList; } public static ResourceAllocationTypeEnum getDefault() { return NON_LIMITING_RESOURCES; } public static ResourceAllocationTypeEnum asEnum(String label) { if (NON_LIMITING_RESOURCES.toString().equals(label)) { return NON_LIMITING_RESOURCES; } else if (LIMITING_RESOURCES.toString().equals(label)) { return LIMITING_RESOURCES; } else if (SUBCONTRACT.toString().equals(label)) { return SUBCONTRACT; } return getDefault(); } } public List<ResourceAllocationTypeEnum> getResourceAllocationTypeOptionList() { return scenarioManager.getCurrent().isMaster() ? ResourceAllocationTypeEnum.getOptionList() : ResourceAllocationTypeEnum.getOptionListForNonMasterBranch(); } public ResourceAllocationTypeEnum getResourceAllocationType() { return getResourceAllocationType(currentTaskElement); } /** * Does nothing, but it must exist for receiving selected value from ListBox. * * @param resourceAllocation */ public void setResourceAllocationType(ResourceAllocationTypeEnum resourceAllocation) { } ResourceAllocationTypeEnum getResourceAllocationType(TaskElement taskElement) { return taskElement == null || !isTask(taskElement) ? null : getResourceAllocationType(asTask(currentTaskElement)); } /** * Returns type of resource allocation depending on state of task. * * If task is subcontracted, return a SUBCONTRACT state. * If task has at least one limiting resource, returns a LIMITING RESOURCE state. * Otherwise, return default state (NON-LIMITING RESOURCE). * * @return {@link ResourceAllocationTypeEnum} */ private ResourceAllocationTypeEnum getResourceAllocationType(Task task) { ResourceAllocationTypeEnum result = ResourceAllocationTypeEnum.NON_LIMITING_RESOURCES; if (task.isSubcontracted()) { result = ResourceAllocationTypeEnum.SUBCONTRACT; } if (task.isLimiting()) { result = ResourceAllocationTypeEnum.LIMITING_RESOURCES; } return result; } private boolean isTask(TaskElement taskElement) { return taskElement instanceof Task; } private Task asTask(TaskElement taskElement) { return (Task) taskElement; } private void changeResourceAllocationType(ResourceAllocationTypeEnum from, ResourceAllocationTypeEnum to) { if (from.equals(ResourceAllocationTypeEnum.NON_LIMITING_RESOURCES)) { fromNonLimitingResource(to); } else if (from.equals(ResourceAllocationTypeEnum.LIMITING_RESOURCES)) { fromLimitingResource(to); } else if (from.equals(ResourceAllocationTypeEnum.SUBCONTRACT)) { fromSubcontract(to); } } /** * Change state from NonLimitingResource assignation type to a new state (limiting, subcontract). * * @param newState */ private void fromNonLimitingResource(ResourceAllocationTypeEnum newState) { if (!isTask(currentTaskElement)) { return; } Task task = asTask(currentTaskElement); if (task.hasResourceAllocations()) { if (Messagebox.show(_("Assigned resources for this task will be deleted. Are you sure?"), _(WARNING), Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION) == Messagebox.OK) { task.removeAllResourceAllocations(); setStateTo(newState); } else { resetStateTo(ResourceAllocationTypeEnum.NON_LIMITING_RESOURCES); } return; } setStateTo(newState); } private void setStateTo(ResourceAllocationTypeEnum state) { setOldState(state); editTaskController.showTabPanel(state); } private void resetStateTo(ResourceAllocationTypeEnum state) { setResourceAllocationType(lbResourceAllocationType, state); setOldState(state); } /** * Change state from LimitingResource assignation type to a new state (non-limiting, subcontract). * * @param newState */ private void fromLimitingResource(ResourceAllocationTypeEnum newState) { if (!isTask(currentTaskElement)) { return; } Task task = asTask(currentTaskElement); if (task.hasResourceAllocations()) { if (Messagebox.show(_("Assigned resources for this task will be deleted. Are you sure?"), _(WARNING), Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION) == Messagebox.OK) { task.removeAllResourceAllocations(); setStateTo(newState); } else { resetStateTo(ResourceAllocationTypeEnum.LIMITING_RESOURCES); } return; } setStateTo(newState); } /** * Change state from Subcontract assignation type to a new state (non-limiting, limiting). * * @param newState */ private void fromSubcontract(ResourceAllocationTypeEnum newState) { Task task = asTask(currentTaskElement); if (task.isSubcontracted()) { final Date communicationDate = (task.getSubcontractedTaskData() != null) ? task.getSubcontractedTaskData().getSubcontractCommunicationDate() : null; // Notification has been sent if (communicationDate != null) { if (Messagebox.show(_( "IMPORTANT: Don't forget to communicate to subcontractor that his contract has been cancelled"), _(WARNING), Messagebox.OK, Messagebox.EXCLAMATION) == Messagebox.OK) { setStateTo(newState); } else { resetStateTo(ResourceAllocationTypeEnum.SUBCONTRACT); } return; } } setStateTo(newState); } boolean stateHasChanged() { final ResourceAllocationTypeEnum currentState = getCurrentState(); return currentState != null && !currentState.equals(getOriginalState()); } ResourceAllocationTypeEnum getOriginalState() { return originalState; } ResourceAllocationTypeEnum getCurrentState() { return getSelectedResourceAllocationType(); } private ResourceAllocationTypeEnum getSelectedResourceAllocationType() { final Listitem item = lbResourceAllocationType.getSelectedItem(); if (item == null) { return null; } final Listcell cell = (Listcell) item.getChildren().get(0); return ResourceAllocationTypeEnum.asEnum(cell.getLabel()); } private boolean isConsolidatedTask() { Task task = asTask(currentTaskElement); return task != null && task.hasConsolidations(); } public void updateTaskEndDate(LocalDate endDate) { getGanttTaskDTO().endDate = endDate.toDateTimeAtStartOfDay().toDate(); Util.reloadBindings(endDateBox); } public void updateTaskStartDate(LocalDate newStart) { getGanttTaskDTO().beginDate = newStart.toDateTimeAtStartOfDay().toDate(); Util.reloadBindings(startDateBox); } TaskEditFormComposer getTaskEditFormComposer() { return taskEditFormComposer; } public void refreshTaskEndDate() { Util.reloadBindings(endDateBox); } public String getMoneyFormat() { return Util.getMoneyFormat(); } /** * Check if resources in allocation are bound by user and in what ROLE they are. * setUser method calling manually because, after initialization user will be null. * Then send valid data to notification_queue table. */ public void emailNotificationAddNew() { proceedList(EmailTemplateEnum.TEMPLATE_TASK_ASSIGNED_TO_RESOURCE, listToAdd); proceedList(EmailTemplateEnum.TEMPLATE_RESOURCE_REMOVED_FROM_TASK, listToDelete); listToAdd.clear(); listToDelete.clear(); } private void proceedList(EmailTemplateEnum enumeration, List<Resource> list) { if (!list.isEmpty()) { List<Worker> workersList = workerModel.getWorkers(); Worker currentWorker; Resource currentResource; for (Worker aWorkersList : workersList) for (Resource aList : list) { currentWorker = aWorkersList; currentResource = aList; if (currentWorker.getId().equals(currentResource.getId())) { aWorkersList.setUser(workerModel.getBoundUserFromDB(currentWorker)); User currentUser = currentWorker.getUser(); if (currentUser != null && (currentUser.isInRole(UserRole.ROLE_EMAIL_TASK_ASSIGNED_TO_RESOURCE) || currentUser.isInRole(UserRole.ROLE_EMAIL_RESOURCE_REMOVED_FROM_TASK))) { setEmailNotificationEntity(enumeration, currentResource); } break; } } } } private void setEmailNotificationEntity(EmailTemplateEnum enumeration, Resource resource) { try { emailNotificationModel.setNewObject(); if (enumeration.equals(EmailTemplateEnum.TEMPLATE_TASK_ASSIGNED_TO_RESOURCE)) { emailNotificationModel.setType(EmailTemplateEnum.TEMPLATE_TASK_ASSIGNED_TO_RESOURCE); } else if (enumeration.equals(EmailTemplateEnum.TEMPLATE_RESOURCE_REMOVED_FROM_TASK)) { emailNotificationModel.setType(EmailTemplateEnum.TEMPLATE_RESOURCE_REMOVED_FROM_TASK); } emailNotificationModel.setUpdated(new Date()); emailNotificationModel.setResource(resource); emailNotificationModel.setTask(currentTaskElement.getTaskSource().getTask()); emailNotificationModel.setProject(currentTaskElement.getParent().getTaskSource().getTask()); emailNotificationModel.confirmSave(); } catch (DataIntegrityViolationException e) { Messagebox.show(_("You cannot email user twice with the same info"), _("Error"), Messagebox.OK, Messagebox.ERROR); } } public List<Resource> getListToDelete() { return listToDelete; } public List<Resource> getListToAdd() { return listToAdd; } public void setResourcesAdded(boolean resourcesAdded) { isResourcesAdded = resourcesAdded; } public TaskPropertiesController getObject() { return this; } }