diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/IMonteCarloModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/IMonteCarloModel.java index 9786da2a8..881a38966 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/IMonteCarloModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/IMonteCarloModel.java @@ -35,12 +35,14 @@ import org.navalplanner.business.planner.entities.TaskElement; */ public interface IMonteCarloModel { - List getCriticalPathTasks(); - - Map calculateMonteCarlo(int times); + Map calculateMonteCarlo(List tasks, int times); void setCriticalPath(Order order, List criticalPath); String getOrderName(); + List getCriticalPathNames(); + + List getCriticalPath(String name); + } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloController.java index 648643eae..a0fff9b8b 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloController.java @@ -30,7 +30,6 @@ import java.util.Map; import org.joda.time.LocalDate; import org.navalplanner.business.orders.entities.Order; import org.navalplanner.web.common.Util; -import org.navalplanner.web.planner.allocation.streches.IStretchesFunctionModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -48,11 +47,12 @@ import org.zkoss.zul.Decimalbox; import org.zkoss.zul.Grid; import org.zkoss.zul.Intbox; import org.zkoss.zul.Label; +import org.zkoss.zul.Listbox; +import org.zkoss.zul.Listitem; import org.zkoss.zul.Row; import org.zkoss.zul.RowRenderer; import org.zkoss.zul.Rows; import org.zkoss.zul.SimpleListModel; -import org.zkoss.zul.XYModel; import org.zkoss.zul.api.Window; /** @@ -81,6 +81,8 @@ public class MonteCarloController extends GenericForwardComposer { private Checkbox cbGroupByWeeks; + private Listbox lbCriticalPaths; + private Window monteCarloChartWindow; public MonteCarloController() { @@ -90,7 +92,17 @@ public class MonteCarloController extends GenericForwardComposer { @Override public void doAfterCompose(org.zkoss.zk.ui.Component comp) throws Exception { super.doAfterCompose(comp); + ibIterations.setValue(DEFAULT_ITERATIONS); + lbCriticalPaths.addEventListener(Events.ON_SELECT, new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + reloadGridCritialPathTasks(); + } + + }); + btnRunMonteCarlo.addEventListener(Events.ON_CLICK, new EventListener() { @Override @@ -98,7 +110,7 @@ public class MonteCarloController extends GenericForwardComposer { int iterations = getIterations(); validateRowsPercentages(); Map monteCarloData = monteCarloModel - .calculateMonteCarlo(iterations); + .calculateMonteCarlo(getSelectedCriticalPath(), iterations); showMonteCarloGraph(monteCarloData); } @@ -169,35 +181,39 @@ public class MonteCarloController extends GenericForwardComposer { } }); + feedCriticalPathsList(); reloadGridCritialPathTasks(); } + private void feedCriticalPathsList() { + lbCriticalPaths.setModel(new SimpleListModel(monteCarloModel + .getCriticalPathNames())); + } + private void reloadGridCritialPathTasks() { - gridCriticalPathTasks.setModel(new SimpleListModel( - getCriticalPathTasks())); + List selectedCriticalPath = getSelectedCriticalPath(); + if (selectedCriticalPath != null) { + gridCriticalPathTasks.setModel(new SimpleListModel( + selectedCriticalPath)); + } if (gridCriticalPathTasks.getRowRenderer() == null) { gridCriticalPathTasks.setRowRenderer(gridCriticalPathTasksRender); } } - public List getCriticalPathTasks() { - return monteCarloModel.getCriticalPathTasks(); + public List getSelectedCriticalPath() { + Listitem selectedItem = lbCriticalPaths.getSelectedItem(); + String selectedPath = selectedItem != null ? selectedItem.getLabel() + : null; + return monteCarloModel.getCriticalPath(selectedPath); } public void setCriticalPath(Order order, List criticalPath) { monteCarloModel.setCriticalPath(order, criticalPath); - } - - public interface IGraphicGenerator { - - public boolean areChartsEnabled(IStretchesFunctionModel model); - - XYModel getDedicationChart( - IStretchesFunctionModel stretchesFunctionModel); - - XYModel getAccumulatedHoursChartData( - IStretchesFunctionModel stretchesFunctionModel); - + if (lbCriticalPaths != null) { + feedCriticalPathsList(); + reloadGridCritialPathTasks(); + } } private class CriticalPathTasksRender implements RowRenderer { diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloModel.java index 5e0609b74..ccc20a442 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloModel.java @@ -20,9 +20,12 @@ package org.navalplanner.web.montecarlo; +import static org.navalplanner.web.I18nHelper._; + import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -33,10 +36,16 @@ import java.util.Random; import java.util.Set; import org.apache.commons.lang.Validate; +import org.hibernate.Hibernate; import org.joda.time.LocalDate; +import org.navalplanner.business.orders.daos.IOrderDAO; import org.navalplanner.business.orders.entities.Order; +import org.navalplanner.business.planner.entities.Dependency; import org.navalplanner.business.planner.entities.Task; import org.navalplanner.business.planner.entities.TaskElement; +import org.navalplanner.business.planner.entities.TaskGroup; +import org.navalplanner.business.scenarios.IScenarioManager; +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; @@ -51,35 +60,248 @@ import org.springframework.transaction.annotation.Transactional; @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class MonteCarloModel implements IMonteCarloModel { - private List criticalPath = new ArrayList(); + @Autowired + private IOrderDAO orderDAO; + + @Autowired + private IScenarioManager scenarioManager; + + private String CRITICAL_PATH = _("Critical path"); + + private String DEFAULT_CRITICAL_PATH = CRITICAL_PATH + " 1"; private Map> estimationRangesForTasks = new HashMap>(); + private Map> criticalPaths = new HashMap>(); + + private String orderName = ""; + + private List orderTasks = new ArrayList(); + @Override @Transactional(readOnly = true) - public void setCriticalPath(Order order, List criticalPath) { - this.criticalPath.clear(); - for (TaskElement each : sortByStartDate(criticalPath)) { - if (each instanceof Task) { - this.criticalPath.add(MonteCarloTask.create((Task) each)); - } + public void setCriticalPath(Order order, List tasksInCriticalPath) { + useSchedulingDataForCurrentScenario(order); + orderTasks.addAll(order.getAllChildrenAssociatedTaskElements()); + initializeTasks(orderTasks); + + initializeOrderName(tasksInCriticalPath); + + int i = 1; + criticalPaths.clear(); + List> allCriticalPaths = buildAllPossibleCriticalPaths(sortByStartDate(onlyTasks((tasksInCriticalPath)))); + for (List path : allCriticalPaths) { + criticalPaths.put(CRITICAL_PATH + " " + i++, toMonteCarloTaskList(path)); } } - private List sortByStartDate(List tasks) { + private void useSchedulingDataForCurrentScenario(Order order) { + orderDAO.reattach(order); + order.useSchedulingDataFor(scenarioManager.getCurrent()); + } + + private void initializeTasks(List taskElements) { + for (TaskElement each: taskElements) { + initializeTaskElement(each); + } + } + + private void initializeTaskElement(TaskElement taskElement) { + if (taskElement == null) { + return; + } + Hibernate.initialize(taskElement); + initializeTaskElement(taskElement.getParent()); + initializeDependencies(taskElement); + } + + private void initializeDependencies(TaskElement taskElement) { + for (Dependency each: taskElement.getDependenciesWithThisDestination()) { + Hibernate.initialize(each.getOrigin()); + Hibernate.initialize(each.getDestination()); + } + } + + private void initializeOrderName(List tasksInCriticalPath) { + orderName = tasksInCriticalPath.isEmpty() ? "" : tasksInCriticalPath + .get(0).getOrderElement().getOrder().getName(); + } + + @Override + public List getCriticalPathNames() { + List result = new ArrayList(criticalPaths.keySet()); + Collections.sort(result); + return result; + } + + @Override + public List getCriticalPath(String name) { + if (name == null || name.isEmpty()) { + return criticalPaths.get(DEFAULT_CRITICAL_PATH); + } + return criticalPaths.get(name); + } + + private List sortByStartDate(List tasks) { Collections.sort(tasks, Task.getByStartDateComparator()); return tasks; } - @Override - public List getCriticalPathTasks() { - return criticalPath; + private List onlyTasks(List tasks) { + List result = new ArrayList(); + for (TaskElement each: tasks) { + if (each instanceof Task) { + result.add((Task) each); + } + } + return result; + } + private List> buildAllPossibleCriticalPaths( + List tasksInCriticalPath) { + + List> result = new ArrayList>(); + + List> allPaths = new ArrayList>(); + for (Task each: tasksWithoutIncomingDependencies(tasksInCriticalPath)) { + buildAllPossiblePaths(each, new ArrayList(), allPaths); + } + for (List path: allPaths) { + if (isCriticalPath(path, tasksInCriticalPath)) { + result.add(path); + } + } + return result; + } + + private List getDestinationsFrom(Task _task) { + List result = new ArrayList(), tasks; + + Task task = retrieveTaskFromModel(_task); + tasks = getDestinationsFrom(task.getDependenciesWithThisOrigin()); + if (!tasks.isEmpty()) { + result.addAll(tasks); + } else { + tasks = getDestinationsFrom(task.getParent().getDependenciesWithThisOrigin()); + if (!tasks.isEmpty()) { + result.addAll(tasks); + } + } + return result; + } + + private List getDestinationsFrom(Set dependencies) { + List result = new ArrayList(); + for (Dependency each: dependencies) { + TaskElement taskElement = each.getDestination(); + if (taskElement instanceof TaskGroup) { + final TaskGroup taskGroup = (TaskGroup) taskElement; + result.addAll(toTaskIfNecessary(taskGroup.getChildren())); + } + if (taskElement instanceof Task) { + result.add((Task) taskElement); + } + } + return result; + } + + private Collection toTaskIfNecessary( + List taskElements) { + List result = new ArrayList(); + for (TaskElement each: taskElements) { + if (each instanceof TaskGroup) { + final TaskGroup taskGroup = (TaskGroup) each; + result.addAll(toTaskIfNecessary(taskGroup.getChildren())); + } + if (each instanceof Task) { + result.add((Task) each); + } + } + return result; + } + + private List tasksWithoutIncomingDependencies(List tasks) { + List result = new ArrayList(); + for (Task each: tasks) { + if (getIncomingDependencies(each).isEmpty()) { + result.add(each); + } + } + return result; + } + + private List getIncomingDependencies(Task _task) { + Task task = retrieveTaskFromModel(_task); + if (task == null) { + throw new RuntimeException(_("Couldn't find %s in model", _task.getName())); + } + List result = new ArrayList(); + result.addAll(task.getDependenciesWithThisDestination()); + result.addAll(task.getParent().getDependenciesWithThisDestination()); + return result; + } + + private Task retrieveTaskFromModel(Task task) { + for (TaskElement each: orderTasks) { + if (each.getId().equals(task.getId())) { + return (Task) each; + } + } + return null; + } + + private boolean isCriticalPath(List path, + List tasksInCriticalPath) { + final List tasksInCriticalPathIds = getIds(tasksInCriticalPath); + for (Task each: path) { + if (!tasksInCriticalPathIds.contains(each.getId())) { + return false; + } + } + return true; + } + + private List getIds(List tasks) { + List result = new ArrayList(); + for (Task each: tasks) { + result.add(each.getId()); + } + return result; + } + + private void buildAllPossiblePaths(Task task, List path, List> allPaths) { + List destinations = getDestinationsFrom(task); + if (destinations.size() == 0) { + path.add(task); + allPaths.add(path); + return; + } + for (Task each: destinations) { + List oldPath = copyPath(path); + path.add(task); + buildAllPossiblePaths((Task) each, path, allPaths); + path = oldPath; + } + } + + private List copyPath(List path) { + List result = new ArrayList(); + for (Task each: path) { + result.add(each); + } + return result; + } + + private List toMonteCarloTaskList(List path) { + List result = new ArrayList(); + for (Task each: path) { + result.add(MonteCarloTask.create(each)); + } + return result; } @Override - public Map calculateMonteCarlo(int iterations) { + public Map calculateMonteCarlo(List tasks, int iterations) { Map monteCarloValues = new HashMap(); - List tasks = copy(criticalPath); adjustDurationDays(tasks); initializeEstimationRanges(tasks); @@ -104,18 +326,7 @@ public class MonteCarloModel implements IMonteCarloModel { } public String getOrderName() { - if (criticalPath.isEmpty()) { - return ""; - } - return criticalPath.get(0).getOrderName(); - } - - private List copy(List tasks) { - List result = new ArrayList(); - for (MonteCarloTask each: tasks) { - result.add(MonteCarloTask.copy(each)); - } - return result; + return orderName; } private void adjustDurationDays(List tasks) { diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloTask.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloTask.java index 626fcf14d..0a7ed112b 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloTask.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/montecarlo/MonteCarloTask.java @@ -162,7 +162,7 @@ public class MonteCarloTask { } public String toString() { - return String.format("%s:%f:(%f,%f):(%f,%f):(%f,%f)", task.getName(), + return String.format("%s:%f:(%f,%d):(%f,%d):(%f,%d)", task.getName(), duration, pessimisticDuration, pessimisticDurationPercentage, normalDuration, normalDurationPercentage, optimisticDuration, optimisticDurationPercentage); diff --git a/navalplanner-webapp/src/main/webapp/montecarlo/_montecarlo.zul b/navalplanner-webapp/src/main/webapp/montecarlo/_montecarlo.zul index 44a6fa66a..8c80d938c 100644 --- a/navalplanner-webapp/src/main/webapp/montecarlo/_montecarlo.zul +++ b/navalplanner-webapp/src/main/webapp/montecarlo/_montecarlo.zul @@ -55,6 +55,23 @@ + + + + + + + + + + + + + + +