diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java index 3ce190c4c..b2697b07f 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java @@ -4,6 +4,7 @@ import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.util.List; +import org.apache.commons.lang.Validate; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; @@ -19,19 +20,19 @@ import org.springframework.orm.hibernate3.SessionFactoryUtils; * An implementation of IGenericDao based on Hibernate's native * API. Concrete DAOs must extend directly from this class. This constraint is * imposed by the constructor of this class that must infer the type of the - * entity from the declaration of the concrete DAO.

- * + * entity from the declaration of the concrete DAO. + *

* This class autowires a SessionFactory bean and allows to * implement DAOs with Hibernate's native API. Subclasses access Hibernate's * Session by calling on getSession() method. * Operations must be implemented by catching HibernateException * and rethrowing it by using convertHibernateAccessException() * method. See source code of this class for an example. - * * @author Fernando Bellas Permuy - * - * @param Entity class - * @param Primary key class + * @param + * Entity class + * @param + * Primary key class */ public class GenericDaoHibernate implements IGenericDao { @@ -47,6 +48,11 @@ public class GenericDaoHibernate implements .getGenericSuperclass()).getActualTypeArguments()[0]; } + public GenericDaoHibernate(Class entityClass) { + Validate.notNull(entityClass); + this.entityClass = entityClass; + } + protected Session getSession() { return sessionFactory.getCurrentSession(); } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/ValidationException.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/ValidationException.java index 77ff08314..3ea2fa958 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/ValidationException.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/ValidationException.java @@ -40,4 +40,8 @@ public class ValidationException extends Exception { this.invalidValues = invalidValues; } + public ValidationException(String message) { + this(new InvalidValue[] {}, message); + } + } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/workorders/entities/ProjectWork.java b/navalplanner-business/src/main/java/org/navalplanner/business/workorders/entities/ProjectWork.java new file mode 100644 index 000000000..c71c7d372 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/workorders/entities/ProjectWork.java @@ -0,0 +1,97 @@ +package org.navalplanner.business.workorders.entities; + +import java.util.Date; + +import org.hibernate.validator.NotEmpty; +import org.hibernate.validator.NotNull; + +/** + * It represents a project with its related information.
+ * @author Óscar González Fernández + */ +public class ProjectWork { + + private static Date copy(Date date) { + return date != null ? new Date(date.getTime()) : date; + } + + private Long id; + + private Long version; + + @NotEmpty + private String name; + + @NotNull + private Date initDate; + + private Date endDate; + + private String description; + + private String responsible; + + // TODO turn into a many to one relationship when Customer entity is defined + private String customer; + + public Long getId() { + return id; + } + + public Long getVersion() { + return version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getInitDate() { + return copy(initDate); + } + + public void setInitDate(Date initDate) { + this.initDate = initDate; + } + + public Date getEndDate() { + return copy(endDate); + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getResponsible() { + return responsible; + } + + public void setResponsible(String responsible) { + this.responsible = responsible; + } + + public String getCustomer() { + return customer; + } + + public void setCustomer(String customer) { + this.customer = customer; + } + + public boolean isEndDateBeforeStart() { + return endDate != null && endDate.before(initDate); + } + +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/workorders/services/IProjectWorkService.java b/navalplanner-business/src/main/java/org/navalplanner/business/workorders/services/IProjectWorkService.java new file mode 100644 index 000000000..6e7db2ba3 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/workorders/services/IProjectWorkService.java @@ -0,0 +1,25 @@ +package org.navalplanner.business.workorders.services; + +import java.util.List; + +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.workorders.entities.ProjectWork; + +/** + * Management of {@link ProjectWork}
+ * @author Óscar González Fernández + */ +public interface IProjectWorkService { + + void save(ProjectWork projectWork) throws ValidationException; + + boolean exists(ProjectWork projectWork); + + List getProjectWorks(); + + void remove(ProjectWork projectWork) throws InstanceNotFoundException; + + ProjectWork find(Long workerId) throws InstanceNotFoundException; + +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/workorders/services/ProjectWorkService.java b/navalplanner-business/src/main/java/org/navalplanner/business/workorders/services/ProjectWorkService.java new file mode 100644 index 000000000..650b00515 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/workorders/services/ProjectWorkService.java @@ -0,0 +1,73 @@ +package org.navalplanner.business.workorders.services; + +import java.util.List; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.navalplanner.business.common.daos.impl.GenericDaoHibernate; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.workorders.entities.ProjectWork; +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.springframework.transaction.annotation.Transactional; + +/** + * Default implementation of {@link IProjectWorkService}
+ * @author Óscar González Fernández + */ +@Component +@Scope(BeanDefinition.SCOPE_SINGLETON) +@Transactional +public class ProjectWorkService implements IProjectWorkService { + + @Autowired + private SessionFactory sessionFactory; + + /* + * Because the dao for project work doesn't have special needs, it's not + * created an interface for defining its contract + */ + + private GenericDaoHibernate dao = new GenericDaoHibernate() { + + @Override + protected Session getSession() { + return sessionFactory.getCurrentSession(); + } + }; + + @Override + @Transactional(readOnly = true) + public boolean exists(ProjectWork projectWork) { + return dao.exists(projectWork.getId()); + } + + @Override + public void save(ProjectWork projectWork) throws ValidationException { + if (projectWork.isEndDateBeforeStart()) { + throw new ValidationException("endDate must be after startDate"); + } + dao.save(projectWork); + } + + @Override + public List getProjectWorks() { + return dao.list(ProjectWork.class); + } + + @Override + public ProjectWork find(Long projectWorkId) + throws InstanceNotFoundException { + return dao.find(projectWorkId); + } + + @Override + public void remove(ProjectWork projectWork) + throws InstanceNotFoundException { + dao.remove(projectWork.getId()); + } + +} diff --git a/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml b/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml index 6ffb33648..760900fee 100644 --- a/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml +++ b/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml @@ -23,6 +23,9 @@ org/navalplanner/business/resources/entities/Resources.hbm.xml + + org/navalplanner/business/resources/entities/WorkOrders.hbm.xml + diff --git a/navalplanner-business/src/main/resources/org/navalplanner/business/resources/entities/WorkOrders.hbm.xml b/navalplanner-business/src/main/resources/org/navalplanner/business/resources/entities/WorkOrders.hbm.xml new file mode 100644 index 000000000..f6c5dab2b --- /dev/null +++ b/navalplanner-business/src/main/resources/org/navalplanner/business/resources/entities/WorkOrders.hbm.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/workorders/services/ProjectWorkServiceTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/workorders/services/ProjectWorkServiceTest.java new file mode 100644 index 000000000..27f9e7f36 --- /dev/null +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/workorders/services/ProjectWorkServiceTest.java @@ -0,0 +1,86 @@ +package org.navalplanner.business.test.workorders.services; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.test.resources.daos.CriterionSatisfactionDAOTest; +import org.navalplanner.business.workorders.entities.ProjectWork; +import org.navalplanner.business.workorders.services.IProjectWorkService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE; +import static org.navalplanner.business.test.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_TEST_FILE; + +/** + * Tests for {@link ProjectWork}.
+ * @author Óscar González Fernández + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE, + BUSINESS_SPRING_CONFIG_TEST_FILE }) +@Transactional +public class ProjectWorkServiceTest { + + private static ProjectWork createValidProjectWork() { + ProjectWork projectWork = new ProjectWork(); + projectWork.setDescription("description"); + projectWork.setCustomer("blabla"); + projectWork.setInitDate(CriterionSatisfactionDAOTest.year(2000)); + projectWork.setName("name"); + projectWork.setResponsible("responsible"); + return projectWork; + } + + @Autowired + private IProjectWorkService projectWorkService; + + @Test + public void testCreation() throws ValidationException { + ProjectWork projectWork = createValidProjectWork(); + projectWorkService.save(projectWork); + assertTrue(projectWorkService.exists(projectWork)); + } + + @Test + public void testListing() throws Exception { + List list = projectWorkService.getProjectWorks(); + projectWorkService.save(createValidProjectWork()); + assertThat(projectWorkService.getProjectWorks().size(), equalTo(list + .size() + 1)); + } + + @Test + public void testRemove() throws Exception { + ProjectWork projectWork = createValidProjectWork(); + projectWorkService.save(projectWork); + assertTrue(projectWorkService.exists(projectWork)); + projectWorkService.remove(projectWork); + assertFalse(projectWorkService.exists(projectWork)); + } + + @Test(expected = ValidationException.class) + public void shouldSendValidationExceptionIfEndDateIsBeforeThanStartingDate() + throws ValidationException { + ProjectWork projectWork = createValidProjectWork(); + projectWork.setEndDate(CriterionSatisfactionDAOTest.year(0)); + projectWorkService.save(projectWork); + } + + @Test + public void testFind() throws Exception { + ProjectWork projectWork = createValidProjectWork(); + projectWorkService.save(projectWork); + assertThat(projectWorkService.find(projectWork.getId()), notNullValue()); + } + +} diff --git a/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml b/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml index 982dbcde8..9e6419d39 100644 --- a/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml +++ b/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml @@ -29,6 +29,9 @@ org/navalplanner/business/resources/entities/Resources.hbm.xml + + org/navalplanner/business/resources/entities/WorkOrders.hbm.xml + diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/MessagesForUser.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/MessagesForUser.java index 14233d109..e55441af8 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/MessagesForUser.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/MessagesForUser.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import org.apache.commons.lang.StringUtils; import org.hibernate.validator.InvalidValue; import org.navalplanner.business.common.exceptions.ValidationException; import org.zkoss.zk.ui.Component; @@ -135,6 +136,9 @@ public class MessagesForUser extends GenericForwardComposer implements for (InvalidValue invalidValue : e.getInvalidValues()) { invalidValue(invalidValue); } + if (!StringUtils.isEmpty(e.getMessage())) { + showMessage(Level.INFO, e.getMessage()); + } } } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/workorders/IProjectWorkModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/workorders/IProjectWorkModel.java new file mode 100644 index 000000000..4beec6d0c --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/workorders/IProjectWorkModel.java @@ -0,0 +1,28 @@ +package org.navalplanner.web.workorders; + +import java.util.List; + +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.workorders.entities.ProjectWork; + +/** + * Contract for {@link ProjectWorkModel}
+ * @author Óscar González Fernández + */ +public interface IProjectWorkModel { + + List getProjects(); + + void prepareEditFor(ProjectWork project); + + void prepareForCreate(); + + void save() throws ValidationException; + + ProjectWork getProject(); + + void remove(ProjectWork projectWork); + + void prepareForRemove(ProjectWork project); + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/workorders/ProjectWorkCRUDController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/workorders/ProjectWorkCRUDController.java new file mode 100644 index 000000000..40119be80 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/workorders/ProjectWorkCRUDController.java @@ -0,0 +1,133 @@ +package org.navalplanner.web.workorders; + +import java.util.List; + +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.workorders.entities.ProjectWork; +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.util.GenericForwardComposer; +import org.zkoss.zul.api.Window; + +/** + * Controller for CRUD actions
+ * @author Óscar González Fernández + */ +public class ProjectWorkCRUDController extends GenericForwardComposer { + + private IProjectWorkModel projectWorkModel; + + private IMessagesForUser messagesForUser; + + private Component messagesContainer; + + private Component editWindow; + + private Component createWindow; + + private Component listWindow; + + private OnlyOneVisible cachedOnlyOneVisible; + + private Window confirmRemove; + + public List getProjects() { + return projectWorkModel.getProjects(); + } + + private OnlyOneVisible getVisibility() { + if (cachedOnlyOneVisible == null) { + cachedOnlyOneVisible = new OnlyOneVisible(listWindow, editWindow, + createWindow); + } + return cachedOnlyOneVisible; + } + + public ProjectWork getProject() { + return projectWorkModel.getProject(); + } + + public void save() { + try { + projectWorkModel.save(); + messagesForUser.showMessage(Level.INFO, "proxecto gardado"); + goToList(); + } catch (ValidationException e) { + messagesForUser.showInvalidValues(e); + } + } + + private void goToList() { + Util.reloadBindings(listWindow); + getVisibility().showOnly(listWindow); + } + + public void cancel() { + goToList(); + } + + public void confirmRemove(ProjectWork project) { + projectWorkModel.prepareForRemove(project); + showConfirmingWindow(); + } + + public void cancelRemove() { + confirmingRemove = false; + confirmRemove.setVisible(false); + Util.reloadBindings(confirmRemove); + } + + private boolean confirmingRemove = false; + + public boolean isConfirmingRemove() { + return confirmingRemove; + } + + private void hideConfirmingWindow() { + confirmingRemove = false; + Util.reloadBindings(confirmRemove); + } + + private void showConfirmingWindow() { + confirmingRemove = true; + try { + Util.reloadBindings(confirmRemove); + confirmRemove.doModal(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void goToEditForm(ProjectWork project) { + projectWorkModel.prepareEditFor(project); + getVisibility().showOnly(editWindow); + Util.reloadBindings(editWindow); + } + + public void remove(ProjectWork projectWork) { + projectWorkModel.remove(projectWork); + hideConfirmingWindow(); + Util.reloadBindings(listWindow); + messagesForUser.showMessage(Level.INFO, "removed " + + projectWork.getName()); + } + + public void goToCreateForm() { + projectWorkModel.prepareForCreate(); + getVisibility().showOnly(createWindow); + Util.reloadBindings(createWindow); + } + + @Override + public void doAfterCompose(Component comp) throws Exception { + super.doAfterCompose(comp); + messagesForUser = new MessagesForUser(messagesContainer); + comp.setVariable("controller", this, true); + getVisibility().showOnly(listWindow); + } + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/workorders/ProjectWorkModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/workorders/ProjectWorkModel.java new file mode 100644 index 000000000..5e8b184f2 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/workorders/ProjectWorkModel.java @@ -0,0 +1,92 @@ +package org.navalplanner.web.workorders; + +import java.util.Date; +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.hibernate.validator.ClassValidator; +import org.hibernate.validator.InvalidValue; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.workorders.entities.ProjectWork; +import org.navalplanner.business.workorders.services.IProjectWorkService; +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.springframework.transaction.annotation.Transactional; + +/** + * Model for UI operations related to {@link ProjectWork}.
+ * @author Óscar González Fernández + */ +@Component +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class ProjectWorkModel implements IProjectWorkModel { + + private final IProjectWorkService projectService; + + private ProjectWork project; + + private ClassValidator projectValidator = new ClassValidator( + ProjectWork.class); + + @Autowired + public ProjectWorkModel(IProjectWorkService projectService) { + Validate.notNull(projectService); + this.projectService = projectService; + } + + @Override + @Transactional(readOnly = true) + public List getProjects() { + return projectService.getProjectWorks(); + } + + @Override + @Transactional(readOnly = true) + public void prepareEditFor(ProjectWork project) { + Validate.notNull(project); + try { + this.project = projectService.find(project.getId()); + } catch (InstanceNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public void prepareForCreate() { + this.project = new ProjectWork(); + this.project.setInitDate(new Date()); + } + + @Override + @Transactional + public void save() throws ValidationException { + InvalidValue[] invalidValues = projectValidator + .getInvalidValues(project); + if (invalidValues.length > 0) + throw new ValidationException(invalidValues); + this.projectService.save(project); + } + + @Override + public ProjectWork getProject() { + return project; + } + + @Override + public void remove(ProjectWork projectWork) { + try { + this.projectService.remove(projectWork); + } catch (InstanceNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public void prepareForRemove(ProjectWork project) { + this.project = project; + } + +} diff --git a/navalplanner-webapp/src/main/webapp/WEB-INF/i3-label.properties b/navalplanner-webapp/src/main/webapp/WEB-INF/i3-label.properties index 07080d2a3..763c2febc 100644 --- a/navalplanner-webapp/src/main/webapp/WEB-INF/i3-label.properties +++ b/navalplanner-webapp/src/main/webapp/WEB-INF/i3-label.properties @@ -29,3 +29,5 @@ mainmenu.help=Axuda mainmenu.about=Acerca de mainmenu.aclunaga=Aclunaga mainmenu.manage_criterions=Administrar criterios +mainmenu.workorders=Traballos +mainmenu.list_projects=Proxectos \ No newline at end of file diff --git a/navalplanner-webapp/src/main/webapp/WEB-INF/i3-label_en_US.properties b/navalplanner-webapp/src/main/webapp/WEB-INF/i3-label_en_US.properties index 182323798..9136891e7 100644 --- a/navalplanner-webapp/src/main/webapp/WEB-INF/i3-label_en_US.properties +++ b/navalplanner-webapp/src/main/webapp/WEB-INF/i3-label_en_US.properties @@ -29,3 +29,5 @@ mainmenu.help=Help mainmenu.about=About mainmenu.aclunaga=Aclunaga mainmenu.manage_criterions=Manage criterions +mainmenu.workorders=Work +mainmenu.list_projects=Projects \ No newline at end of file diff --git a/navalplanner-webapp/src/main/webapp/common/layout/template.zul b/navalplanner-webapp/src/main/webapp/common/layout/template.zul index da2e7497b..1cf9c1e51 100644 --- a/navalplanner-webapp/src/main/webapp/common/layout/template.zul +++ b/navalplanner-webapp/src/main/webapp/common/layout/template.zul @@ -36,6 +36,13 @@ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +