diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/daos/IScenarioDAO.java b/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/daos/IScenarioDAO.java index 671ea92f1..d07e280e0 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/daos/IScenarioDAO.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/daos/IScenarioDAO.java @@ -41,4 +41,6 @@ public interface IScenarioDAO extends IGenericDAO { List getAll(); + boolean thereIsOtherWithSameName(Scenario scenario); + } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/daos/ScenarioDAO.java b/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/daos/ScenarioDAO.java index 10355d1a3..14ce9f349 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/daos/ScenarioDAO.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/daos/ScenarioDAO.java @@ -115,4 +115,22 @@ public class ScenarioDAO extends GenericDAOHibernate implements return list(Scenario.class); } + @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true) + @Override + public boolean thereIsOtherWithSameName(Scenario scenario) { + try { + Scenario withSameName = findByName(scenario.getName()); + return areDifferentInDB(withSameName, scenario); + } catch (InstanceNotFoundException e) { + return false; + } + } + + private boolean areDifferentInDB(Scenario one, Scenario other) { + if ((one.getId() == null) || (other.getId() == null)) { + return true; + } + return !one.getId().equals(other.getId()); + } + } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/entities/Scenario.java b/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/entities/Scenario.java index 254183548..d096d4f84 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/entities/Scenario.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/scenarios/entities/Scenario.java @@ -20,6 +20,8 @@ package org.navalplanner.business.scenarios.entities; +import static org.navalplanner.business.i18n.I18nHelper._; + import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -32,6 +34,7 @@ import org.navalplanner.business.common.BaseEntity; import org.navalplanner.business.common.Registry; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; import org.navalplanner.business.orders.entities.Order; +import org.navalplanner.business.scenarios.bootstrap.PredefinedScenarios; import org.navalplanner.business.scenarios.daos.IScenarioDAO; /** @@ -51,6 +54,8 @@ public class Scenario extends BaseEntity { */ private Map orders = new HashMap(); + private Scenario predecessor = null; + public static Scenario create(String name) { return create(new Scenario(name)); } @@ -67,6 +72,11 @@ public class Scenario extends BaseEntity { this.name = name; } + public Scenario(String name, Scenario predecessor) { + this.name = name; + this.predecessor = predecessor; + } + public void addOrder(Order order) { if (!orders.values().contains(order)) { orders.put(order, OrderVersion.createInitialVersion()); @@ -94,6 +104,10 @@ public class Scenario extends BaseEntity { this.description = description; } + public Scenario getPredecessor() { + return predecessor; + } + @AssertTrue(message = "name is already used") public boolean checkConstraintUniqueName() { if (StringUtils.isBlank(name)) { @@ -115,4 +129,26 @@ public class Scenario extends BaseEntity { } } + public boolean isDerived() { + return predecessor != null; + } + + public Scenario newDerivedScenario() { + return new Scenario(_("Derived from {0}", name), this); + } + + public boolean isPredefined() { + if (name == null) { + return false; + } + + for (PredefinedScenarios predefinedScenario : PredefinedScenarios + .values()) { + if (predefinedScenario.getName().equals(name)) { + return true; + } + } + return false; + } + } \ No newline at end of file diff --git a/navalplanner-business/src/main/resources/org/navalplanner/business/scenarios/entities/Scenarios.hbm.xml b/navalplanner-business/src/main/resources/org/navalplanner/business/scenarios/entities/Scenarios.hbm.xml index 15678fb6e..c627180ea 100644 --- a/navalplanner-business/src/main/resources/org/navalplanner/business/scenarios/entities/Scenarios.hbm.xml +++ b/navalplanner-business/src/main/resources/org/navalplanner/business/scenarios/entities/Scenarios.hbm.xml @@ -21,6 +21,8 @@ + + diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/CustomMenuController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/CustomMenuController.java index 134e9664f..9f8f1fe58 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/CustomMenuController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/CustomMenuController.java @@ -228,7 +228,8 @@ public class CustomMenuController extends Div implements IMenuItemsRegister { subItem(_("Quality forms"),"/qualityforms/qualityForms.zul","12-formularios-calidad.html#administraci-n-de-formularios-de-calidade"), subItem(_("Manage user profiles"), "/users/profiles.zul","13-usuarios.html#administraci-n-de-perfiles"), subItem(_("Manage user accounts"), "/users/users.zul","13-usuarios.html#administraci-n-de-usuarios"), - subItem(_("Manage external companies"), "/externalcompanies/externalcompanies.zul","")); + subItem(_("Manage external companies"), "/externalcompanies/externalcompanies.zul",""), + subItem(_("Manage scenarios"), "/scenarios/scenarios.zul","")); } topItem(_("Reports"), "", "", diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/IScenarioModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/IScenarioModel.java new file mode 100644 index 000000000..57998de3b --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/IScenarioModel.java @@ -0,0 +1,59 @@ +/* + * This file is part of NavalPlan + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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 . + */ + +package org.navalplanner.web.scenarios; + +import java.util.List; + +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.scenarios.entities.Scenario; + +/** + * Contract for {@link ScenarioModel}. + * + * @author Manuel Rego Casasnovas + */ +public interface IScenarioModel { + + /* + * Non conversational steps + */ + List getScenarios(); + + /* + * Initial conversation steps + */ + void initEdit(Scenario scenario); + + void initCreateDerived(Scenario scenario); + + /* + * Intermediate conversation steps + */ + Scenario getScenario(); + + /* + * Final conversation steps + */ + void confirmSave() throws ValidationException; + + void cancel(); + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenarioCRUDController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenarioCRUDController.java new file mode 100644 index 000000000..117a34400 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenarioCRUDController.java @@ -0,0 +1,191 @@ +/* + * This file is part of NavalPlan + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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 . + */ + +package org.navalplanner.web.scenarios; + +import static org.navalplanner.web.I18nHelper._; + +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.scenarios.entities.Scenario; +import org.navalplanner.web.common.IMessagesForUser; +import org.navalplanner.web.common.Level; +import org.navalplanner.web.common.MessagesForUser; +import org.navalplanner.web.common.OnlyOneVisible; +import org.navalplanner.web.common.Util; +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.util.GenericForwardComposer; +import org.zkoss.zul.Button; +import org.zkoss.zul.Label; +import org.zkoss.zul.SimpleTreeNode; +import org.zkoss.zul.Treecell; +import org.zkoss.zul.Treeitem; +import org.zkoss.zul.TreeitemRenderer; +import org.zkoss.zul.Treerow; +import org.zkoss.zul.api.Window; + +/** + * Controller for CRUD actions over a {@link Scenario}. + * + * @author Manuel Rego Casasnovas + */ +public class ScenarioCRUDController extends GenericForwardComposer { + + private IScenarioModel scenarioModel; + + private Window listWindow; + + private Window createWindow; + + private Window editWindow; + + private OnlyOneVisible visibility; + + private IMessagesForUser messagesForUser; + + private Component messagesContainer; + + private ScenariosTreeitemRenderer scenariosTreeitemRenderer = new ScenariosTreeitemRenderer(); + + public Scenario getScenario() { + return scenarioModel.getScenario(); + } + + @Override + public void doAfterCompose(Component comp) throws Exception { + super.doAfterCompose(comp); + messagesForUser = new MessagesForUser(messagesContainer); + comp.setVariable("scenarioController", this, true); + getVisibility().showOnly(listWindow); + } + + public void cancel() { + scenarioModel.cancel(); + goToList(); + } + + public void goToList() { + Util.reloadBindings(listWindow); + getVisibility().showOnly(listWindow); + } + + public void goToEditForm(Scenario scenario) { + scenarioModel.initEdit(scenario); + getVisibility().showOnly(editWindow); + Util.reloadBindings(editWindow); + } + + public void save() { + try { + scenarioModel.confirmSave(); + messagesForUser.showMessage(Level.INFO, _("Scenario \"{0}\" saved", + scenarioModel.getScenario().getName())); + goToList(); + } catch (ValidationException e) { + messagesForUser.showInvalidValues(e); + } + } + + private OnlyOneVisible getVisibility() { + if (visibility == null) { + visibility = new OnlyOneVisible(listWindow, createWindow, + editWindow); + } + return visibility; + } + + public void goToCreateDerivedForm(Scenario scenario) { + scenarioModel.initCreateDerived(scenario); + getVisibility().showOnly(createWindow); + Util.reloadBindings(createWindow); + } + + public ScenariosTreeModel getScenariosTreeModel() { + return new ScenariosTreeModel(new ScenarioTreeRoot(scenarioModel + .getScenarios())); + } + + public ScenariosTreeitemRenderer getScenariosTreeitemRenderer() { + return scenariosTreeitemRenderer; + } + + public class ScenariosTreeitemRenderer implements TreeitemRenderer { + + @Override + public void render(Treeitem item, Object data) throws Exception { + SimpleTreeNode simpleTreeNode = (SimpleTreeNode) data; + final Scenario scenario = (Scenario) simpleTreeNode.getData(); + item.setValue(data); + + Treerow treerow = new Treerow(); + + Treecell nameTreecell = new Treecell(); + Label nameLabel = new Label(scenario.getName()); + nameTreecell.appendChild(nameLabel); + treerow.appendChild(nameTreecell); + + Treecell operationsTreecell = new Treecell(); + + Button createDerivedButton = new Button(); + createDerivedButton.setTooltiptext(_("Create derived")); + createDerivedButton.setSclass("icono"); + createDerivedButton.setImage("/common/img/ico_derived1.png"); + createDerivedButton.setHoverImage("/common/img/ico_derived.png"); + + createDerivedButton.addEventListener(Events.ON_CLICK, + new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + goToCreateDerivedForm(scenario); + } + + }); + operationsTreecell.appendChild(createDerivedButton); + + Button editButton = new Button(); + editButton.setTooltiptext(_("Edit")); + editButton.setSclass("icono"); + editButton.setImage("/common/img/ico_editar1.png"); + editButton.setHoverImage("/common/img/ico_editar.png"); + + editButton.addEventListener(Events.ON_CLICK, new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + goToEditForm(scenario); + } + + }); + operationsTreecell.appendChild(editButton); + + treerow.appendChild(operationsTreecell); + + item.appendChild(treerow); + + // Show the tree expanded at start + item.setOpen(true); + } + + } + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenarioModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenarioModel.java new file mode 100644 index 000000000..e735ba5fe --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenarioModel.java @@ -0,0 +1,130 @@ +/* + * This file is part of NavalPlan + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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 . + */ + +package org.navalplanner.web.scenarios; + +import static org.navalplanner.web.I18nHelper._; + +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.hibernate.validator.InvalidValue; +import org.navalplanner.business.calendars.entities.BaseCalendar; +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.scenarios.daos.IScenarioDAO; +import org.navalplanner.business.scenarios.entities.Scenario; +import org.navalplanner.web.common.concurrentdetection.OnConcurrentModification; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Model for UI operations related to {@link Scenario}. + * + * @author Manuel Rego Casasnovas + */ +@Service +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +@Qualifier("main") +@OnConcurrentModification(goToPage = "/scenarios/scenarios.zul") +public class ScenarioModel implements IScenarioModel { + + /** + * Conversation state + */ + private Scenario scenario; + + @Autowired + private IScenarioDAO scenarioDAO; + + /* + * Non conversational steps + */ + @Override + @Transactional(readOnly = true) + public List getScenarios() { + return scenarioDAO.getAll(); + } + + /* + * Initial conversation steps + */ + @Override + @Transactional(readOnly = true) + public void initEdit(Scenario scenario) { + Validate.notNull(scenario); + forceLoad(scenario); + + this.scenario = scenario; + } + + private void forceLoad(Scenario scenario) { + scenarioDAO.reattach(scenario); + scenario.getOrders().keySet(); + } + + @Override + @Transactional(readOnly = true) + public void initCreateDerived(Scenario scenario) { + Validate.notNull(scenario); + forceLoad(scenario); + + this.scenario = scenario.newDerivedScenario(); + } + + /* + * Intermediate conversation steps + */ + @Override + public Scenario getScenario() { + return scenario; + } + + /* + * Final conversation steps + */ + + @Override + @Transactional + public void confirmSave() throws ValidationException { + if (scenarioDAO.thereIsOtherWithSameName(scenario)) { + InvalidValue[] invalidValues = { new InvalidValue(_( + "{0} already exists", scenario.getName()), + BaseCalendar.class, "name", scenario.getName(), scenario) }; + throw new ValidationException(invalidValues, + _("Could not save the scenario")); + } + + scenarioDAO.save(scenario); + } + + @Override + public void cancel() { + resetState(); + } + + private void resetState() { + scenario = null; + } + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenarioTreeRoot.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenarioTreeRoot.java new file mode 100644 index 000000000..cb025018c --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenarioTreeRoot.java @@ -0,0 +1,69 @@ +/* + * This file is part of NavalPlan + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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 . + */ + +package org.navalplanner.web.scenarios; + +import java.util.ArrayList; +import java.util.List; + +import org.navalplanner.business.scenarios.entities.Scenario; + +/** + * Class that represents a root node for the {@link Scenario} tree. + * + * @author Manuel Rego Casasnovas + */ +public class ScenarioTreeRoot { + + private List rootScenarios = new ArrayList(); + private List derivedScenarios = new ArrayList(); + + /** + * Creates a {@link ScenarioTreeRoot} using the list of {@link Scenario} + * passed as argument. + * + * @param scenarios + * All the {@link Scenario} that will be shown in the tree. + */ + public ScenarioTreeRoot(List scenarios) { + for (Scenario scenario : scenarios) { + if (scenario.isDerived()) { + derivedScenarios.add(scenario); + } else { + rootScenarios.add(scenario); + } + } + } + + /** + * Returns all the {@link Scenario} that has no parent. + */ + public List getRootScenarios() { + return rootScenarios; + } + + /** + * Returns all the {@link Scenario} that has a parent. + */ + public List getDerivedScenarios() { + return derivedScenarios; + } + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenariosTreeModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenariosTreeModel.java new file mode 100644 index 000000000..281416a15 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/scenarios/ScenariosTreeModel.java @@ -0,0 +1,111 @@ +/* + * This file is part of NavalPlan + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * 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 . + */ + +package org.navalplanner.web.scenarios; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.navalplanner.business.scenarios.entities.Scenario; +import org.zkoss.zul.SimpleTreeModel; +import org.zkoss.zul.SimpleTreeNode; + +/** + * Model for the {@link Scenario} tree. + * + * @author Manuel Rego Casasnovas + */ +public class ScenariosTreeModel extends SimpleTreeModel { + + private static Map> relationParentChildren = new HashMap>(); + + public ScenariosTreeModel(ScenarioTreeRoot root) { + super(createRootNodeAndDescendants(root, root.getRootScenarios(), root + .getDerivedScenarios())); + } + + private static SimpleTreeNode createRootNodeAndDescendants( + ScenarioTreeRoot root, List rootScenarios, + List derivedScenarios) { + + fillHashParentChildren(rootScenarios, derivedScenarios); + + return new SimpleTreeNode(root, asNodes(rootScenarios)); + } + + private static List asNodes(List scenarios) { + if (scenarios == null) { + return new ArrayList(); + } + + ArrayList result = new ArrayList(); + for (Scenario scenario : scenarios) { + result.add(asNode(scenario)); + } + + return result; + } + + private static SimpleTreeNode asNode(Scenario scenario) { + List children = relationParentChildren.get(scenario); + return new SimpleTreeNode(scenario, asNodes(children)); + } + + private static void fillHashParentChildren( + List rootScenarios, + List derivedScenarios) { + for (Scenario root : rootScenarios) { + relationParentChildren.put(root, new ArrayList()); + } + + for (Scenario derived : derivedScenarios) { + Scenario parent = derived.getPredecessor(); + List siblings = relationParentChildren.get(parent); + + if (siblings == null) { + siblings = new ArrayList(); + siblings.add(derived); + relationParentChildren.put(parent, siblings); + } else { + siblings.add(derived); + } + } + } + + @Override + public boolean isLeaf(Object node) { + if (node == null) { + return true; + } + + SimpleTreeNode simpleTreeNode = (SimpleTreeNode) node; + Scenario scenario = (Scenario) simpleTreeNode.getData(); + + List children = relationParentChildren.get(scenario); + if (children == null) { + return true; + } + + return children.isEmpty(); + } + +} diff --git a/navalplanner-webapp/src/main/webapp/scenarios/_edition.zul b/navalplanner-webapp/src/main/webapp/scenarios/_edition.zul new file mode 100644 index 000000000..561106c01 --- /dev/null +++ b/navalplanner-webapp/src/main/webapp/scenarios/_edition.zul @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + +