diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/materials/daos/IUnitTypeDAO.java b/navalplanner-business/src/main/java/org/navalplanner/business/materials/daos/IUnitTypeDAO.java index f1474c0af..b4389b4a5 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/materials/daos/IUnitTypeDAO.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/materials/daos/IUnitTypeDAO.java @@ -28,6 +28,7 @@ import org.navalplanner.business.materials.entities.UnitType; /** * @author Susana Montes Pedreira + * @author Javier Moran Rua */ public interface IUnitTypeDAO extends IIntegrationEntityDAO { @@ -39,4 +40,9 @@ public interface IUnitTypeDAO extends IIntegrationEntityDAO { throws InstanceNotFoundException; boolean existsUnitTypeByNameInAnotherTransaction(String measure); + + UnitType findByNameCaseInsensitive(String measure) + throws InstanceNotFoundException; + + boolean isUnitTypeUsedInAnyMaterial(UnitType unitType); } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/materials/daos/UnitTypeDAO.java b/navalplanner-business/src/main/java/org/navalplanner/business/materials/daos/UnitTypeDAO.java index 8ba4b5d82..2bb90a6ea 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/materials/daos/UnitTypeDAO.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/materials/daos/UnitTypeDAO.java @@ -23,9 +23,12 @@ package org.navalplanner.business.materials.daos; import java.util.List; import org.apache.commons.lang.StringUtils; +import org.hibernate.Criteria; +import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Restrictions; import org.navalplanner.business.common.daos.IntegrationEntityDAO; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.materials.entities.Material; import org.navalplanner.business.materials.entities.UnitType; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -36,6 +39,7 @@ import org.springframework.transaction.annotation.Transactional; /** * DAO for {@link UnitType} * @author Susana Montes Pedreira + * @author Javier Moran Rua */ @Repository @Scope(BeanDefinition.SCOPE_SINGLETON) @@ -70,18 +74,41 @@ public class UnitTypeDAO extends IntegrationEntityDAO implements @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) public UnitType findUniqueByNameInAnotherTransaction(String measure) throws InstanceNotFoundException { - return findByName(measure); + return findByNameCaseInsensitive(measure); } @Override @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) public boolean existsUnitTypeByNameInAnotherTransaction(String measure) { try { - findByName(measure); + findByNameCaseInsensitive(measure); } catch (InstanceNotFoundException e) { return false; } return true; } + @Override + @Transactional(readOnly=true) + public UnitType findByNameCaseInsensitive(String measure) + throws InstanceNotFoundException { + Criteria c = getSession().createCriteria(UnitType.class); + c.add(Restrictions.ilike("measure", measure, MatchMode.EXACT)); + UnitType result = (UnitType) c.uniqueResult(); + + if (result == null) { + throw new InstanceNotFoundException(measure, + getEntityClass().getName()); + } + + return result; + } + + @Override + @Transactional(readOnly=true) + public boolean isUnitTypeUsedInAnyMaterial(UnitType unitType) { + Criteria c = getSession().createCriteria(Material.class); + return !c.add(Restrictions.eq("unitType", unitType)).list().isEmpty(); + } + } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/materials/entities/UnitType.java b/navalplanner-business/src/main/java/org/navalplanner/business/materials/entities/UnitType.java index daf011d8d..04e90d096 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/materials/entities/UnitType.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/materials/entities/UnitType.java @@ -32,16 +32,26 @@ import org.navalplanner.business.materials.daos.IUnitTypeDAO; * UnitType entity * * @author Susana Montes Pedreira - * + * @author Javier Moran Rua */ public class UnitType extends IntegrationEntity{ public static UnitType create(String code, String measure) { - return (UnitType) create(new UnitType(measure), code); + UnitType unitType = new UnitType(measure); + unitType.setNewObject(true); + return (UnitType) create(unitType, code); } public static UnitType create(String measure) { - return (UnitType) create(new UnitType(measure)); + UnitType unitType = new UnitType(measure); + unitType.setNewObject(true); + return (UnitType) create(unitType); + } + + public static UnitType create() { + UnitType unitType = new UnitType(); + unitType.setNewObject(true); + return create(unitType); } public void updateUnvalidated(String measure) { 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..14a34dff2 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 @@ -222,6 +222,7 @@ public class CustomMenuController extends Div implements IMenuItemsRegister { subItem(_("Calendars"),"/calendars/calendars.zul", "03-calendarios.html"), subItem(_("Label types"), "/labels/labelTypes.zul","10-etiquetas.html"), subItem(_("Materials"), "/materials/materials.zul", "11-materiales.html#administraci-n-de-materiais"), + subItem(_("Unit types"), "/materials/unitTypes.zul", "11-materiales.html#administraci-n-de-materiais"), subItem(_("Manage cost categories"),"/costcategories/costCategory.zul","14-custos.html#categor-as-de-custo"), subItem(_("Manage types of work hours"),"/costcategories/typeOfWorkHours.zul","14-custos.html#administraci-n-de-horas-traballadas"), subItem(_("Configuration"), "/common/configuration.zul","03-calendarios.html#calendario-por-defecto"), diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/materials/IUnitTypeModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/materials/IUnitTypeModel.java new file mode 100644 index 000000000..02d3c54be --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/materials/IUnitTypeModel.java @@ -0,0 +1,111 @@ +package org.navalplanner.web.materials; + +import java.util.List; + +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.materials.entities.UnitType; + +/** + * Interface for the model which lets the client of this model + * list the unit types, create new unit types, edit existing unit types + * and remove unity types + * + * Conversation state: A unit type being edited or created. + * + * Not conversational methods: + *
    + *
  • getUnitTypes
  • + *
  • existsAnotherUnitTypeWithName
  • + *
  • existsAnotherUnitTypeWithCode
  • + *
  • isUnitTypeUsedInAnyMaterial
  • + *
  • isUnitTypeUsedInAnyMaterial
  • + *
  • remove
  • + *
+ * + * Conversational methods: + *
    + *
  • initCreate
  • + *
  • initEdit
  • + *
  • getCurrentUnitType
  • + *
  • confirmSave
  • + *
+ * + * @author Javier Moran Rua + */ + +public interface IUnitTypeModel { + + // Non conversational methods + + /** + * Query the database to get all the unit types in the database + */ + List getUnitTypes(); + + /** + * This method check if there is another UnitType in the database + * different from the one in the state of the model which had the same + * measure name as the parameter. + * + * @param name the measure name to be checked as unique in the unit types + * @return the boolean with the result + */ + boolean existsAnotherUnitTypeWithName(String name); + + /** + * This method check if there is another UnitType in the database + * different from the one in the state of the model which had teh same + * code as the parameter + * + * @param code the code to be checked as unique + * @return the boolean showing the result + */ + boolean existsAnotherUnitTypeWithCode(String code); + + /** + * This method finds out if the unit type passed as parameter is + * used to measure any material + * + * @param unitType the unitType to check + * @return the boolean with the result + */ + boolean isUnitTypeUsedInAnyMaterial(UnitType unitType); + + /** + * This method removes the unit type passed as parameter from the + * database + * + * @param unitType the unitType which is wanted to be deleted + */ + void remove(UnitType unitType); + + //Conversational methods + + /** + * First method of the conversational state. Prepares the state with the + * unit type to edit + */ + void initEdit(UnitType unitType); + + /** + * First method of the conversational state. Creates an empty unit type + * to be saved + */ + void initCreate(); + + /** + * Get the current unit type which is in the state of the + * + * @return + */ + UnitType getCurrentUnitType(); + + /** + * Last method of the conversation. It ends with the saving of the unit + * type in the state to the database + * + * @throws ValidationException + */ + void confirmSave() throws ValidationException; + +} \ No newline at end of file diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/materials/UnitTypeController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/materials/UnitTypeController.java new file mode 100644 index 000000000..8849a6b8b --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/materials/UnitTypeController.java @@ -0,0 +1,249 @@ +package org.navalplanner.web.materials; + +import static org.navalplanner.web.I18nHelper._; + +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.LogFactory; +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.materials.entities.UnitType; +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.util.logging.Log; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.WrongValueException; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.util.GenericForwardComposer; +import org.zkoss.zul.Constraint; +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.Textbox; +import org.zkoss.zul.impl.InputElement; + +/** + * + * Controller for the listing and editing unit types + * + * @author Javier Moran Rua + */ + +public class UnitTypeController extends GenericForwardComposer { + + private static final org.apache.commons.logging.Log LOG = LogFactory + .getLog(UnitTypeController.class); + + private Component messagesContainer; + private IMessagesForUser messagesForUser; + private OnlyOneVisible visibility; + + private Component listWindow; + private Component editWindow; + + private IUnitTypeModel unitTypeModel; + + @Override + public void doAfterCompose(Component comp) throws Exception { + super.doAfterCompose(comp); + messagesForUser = new MessagesForUser(messagesContainer); + comp.setVariable("controller", this, true); + getVisibility().showOnly(listWindow); + } + + private OnlyOneVisible getVisibility() { + if (visibility == null) { + visibility = new OnlyOneVisible(listWindow,editWindow); + } + return visibility; + } + + public List getUnitTypes() { + return unitTypeModel.getUnitTypes(); + } + + public RowRenderer getUnitTypeRenderer() { + + return new RowRenderer() { + @Override + public void render(Row row, Object data) throws Exception { + UnitType unitType = (UnitType) data; + + appendUnitTypeName(row, unitType); + appendOperations(row, unitType); + } + + private void appendUnitTypeName(Row row, UnitType unitType) { + row.appendChild(new Label(unitType.getMeasure())); + } + + private void appendOperations(Row row, final UnitType unitType) { + Hbox hbox = new Hbox(); + + hbox.appendChild(Util.createEditButton(new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + goToEditFormInEditionMode(unitType); + } + })); + + hbox.appendChild(Util.createRemoveButton(new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + confirmRemove(unitType); + } + })); + + row.appendChild(hbox); + } + }; + } + + private void confirmRemove(UnitType unitType) { + try { + int status = Messagebox.show(_("Confirm deleting {0}. Are you sure?", unitType.getMeasure()), + "Delete", Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION); + if (Messagebox.OK == status) { + removeUnitType(unitType); + } + } catch (InterruptedException e) { + LOG.error("Error showing confirming message box",e); + throw new RuntimeException(); + } + } + + private void removeUnitType(UnitType unitType) { + if (unitTypeModel.isUnitTypeUsedInAnyMaterial(unitType)) { + messagesForUser.showMessage(Level.ERROR, _("Unit {0} cannot be " + + " removed because it is used in materials", + unitType.getMeasure())); + } else { + unitTypeModel.remove(unitType); + Util.reloadBindings(listWindow); + messagesForUser.showMessage(Level.INFO, _("Deleted unit type {0}", + unitType.getMeasure())); + } + } + + public void goToEditFormInCreationMode() { + unitTypeModel.initCreate(); + getVisibility().showOnly(editWindow); + Util.reloadBindings(editWindow); + } + + private void goToEditFormInEditionMode(UnitType unitType) { + unitTypeModel.initEdit(unitType); + getVisibility().showOnly(editWindow); + Util.reloadBindings(editWindow); + } + + public UnitType getUnitType() { + return unitTypeModel.getCurrentUnitType(); + } + + public Constraint uniqueMeasureName() { + return new Constraint() { + + @Override + public void validate(Component comp, Object value) + throws WrongValueException { + String strValue = (String) value; + if (StringUtils.isBlank(strValue)) { + throw new WrongValueException(comp, + _("Unit type name cannot be empty") + ); + } + + if (unitTypeModel.existsAnotherUnitTypeWithName(strValue)) { + throw new WrongValueException(comp, + _("The meausure name is not valid. There is " + + "another unit type with the same " + + "measure name")); + } + } + }; + } + + public Constraint uniqueCode() { + return new Constraint() { + + @Override + public void validate(Component comp, Object value) + throws WrongValueException { + String strValue = (String) value; + if (StringUtils.isBlank(strValue)) { + throw new WrongValueException(comp, + _("Unit type code cannot be empty")); + } + + if (unitTypeModel.existsAnotherUnitTypeWithCode(strValue)) { + throw new WrongValueException(comp, + _("The code is not valid. There is another " + + "unit type with the same code")); + } + } + + }; + } + public void saveAndExit() { + if (save()) { + goToList(); + } + } + + public void saveAndContinue() { + if (save()) { + goToEditFormInEditionMode(getUnitType()); + } + } + + public void cancel() { + goToList(); + } + + private boolean save() { + try { + validateAll(); + unitTypeModel.confirmSave(); + messagesForUser.showMessage(Level.INFO, _("Unit type saved")); + return true; + } catch (ValidationException e) { + messagesForUser.showInvalidValues(e); + return false; + } + } + + private void validateAll() { + Textbox codeTextBox = (Textbox) editWindow. + getFellowIfAny("codeTextBox"); + validate((InputElement) codeTextBox,codeTextBox.getValue()); + + Textbox measureTextBox = (Textbox) editWindow. + getFellowIfAny("measureTextBox"); + validate((InputElement) measureTextBox,measureTextBox.getValue()); + } + + /** + * Validates {@link Textbox} checking {@link Constraint} + * @param comp + */ + private void validate(InputElement comp, Object value) { + if (comp != null && comp.getConstraint() != null) { + final Constraint constraint = comp.getConstraint(); + constraint.validate(comp, value); + } + } + + private void goToList() { + Util.reloadBindings(listWindow); + getVisibility().showOnly(listWindow); + } +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/materials/UnitTypeModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/materials/UnitTypeModel.java new file mode 100644 index 000000000..8cb834cd6 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/materials/UnitTypeModel.java @@ -0,0 +1,138 @@ +package org.navalplanner.web.materials; + +import static org.navalplanner.web.I18nHelper._; + +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.apache.commons.logging.LogFactory; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.business.materials.daos.IUnitTypeDAO; +import org.navalplanner.business.materials.entities.UnitType; +import org.navalplanner.web.common.concurrentdetection.OnConcurrentModification; +import org.springframework.beans.factory.annotation.Autowired; +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 the listing, creation and edition of UnitTypes + * + * @author Javier Moran Rua + * + */ + +@Service +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +@OnConcurrentModification(goToPage = "/materials/unitTypes.zul") +public class UnitTypeModel implements IUnitTypeModel { + + private static final org.apache.commons.logging.Log LOG = LogFactory + .getLog(UnitTypeModel.class); + + // This is the state of the model, just the current unitType being edited + private UnitType unitTypeState; + + @Autowired + private IUnitTypeDAO unitTypeDAO; + + @Override + @Transactional(readOnly=true) + public List getUnitTypes() { + return unitTypeDAO.getAll(); + } + + @Override + public void initCreate() { + this.unitTypeState = UnitType.create(); + } + + @Override + @Transactional(readOnly=true) + public void initEdit(UnitType unitType) { + Validate.notNull(unitType); + this.unitTypeState = getFromDB(unitType); + } + + private UnitType getFromDB(UnitType unitType) { + try { + return unitTypeDAO.find(unitType.getId()); + } catch (InstanceNotFoundException e) { + LOG.error(_("It was not possible load entity. Not found. Id: " + + unitType.getId()), e); + throw new RuntimeException(e); + } + } + + @Override + public UnitType getCurrentUnitType() { + return this.unitTypeState; + } + + @Override + @Transactional + public void confirmSave() throws ValidationException { + unitTypeDAO.save(this.unitTypeState); + } + + @Override + @Transactional(readOnly=true) + public boolean existsAnotherUnitTypeWithName(String name) { + boolean result; + + try { + UnitType foundUnitType = + unitTypeDAO.findByNameCaseInsensitive(name); + result = isTheSameEntityAsState(foundUnitType) ? false : true; + } catch (InstanceNotFoundException e) { + result = false; + } + + return result; + } + + @Override + @Transactional(readOnly=true) + public boolean existsAnotherUnitTypeWithCode(String code) { + boolean result; + + try { + UnitType foundUnitType = + unitTypeDAO.findByCode(code); + result = isTheSameEntityAsState(foundUnitType) ? false : true; + } catch (InstanceNotFoundException e) { + result = false; + } + + return result; + } + + private boolean isTheSameEntityAsState(UnitType foundUnitType) { + if (getCurrentUnitType().isNewObject()) { + return false; + } else { + return foundUnitType.getId().equals(getCurrentUnitType().getId()); + } + } + + @Override + @Transactional(readOnly=true) + public boolean isUnitTypeUsedInAnyMaterial(UnitType unitType) { + return unitTypeDAO.isUnitTypeUsedInAnyMaterial(unitType); + } + + @Override + @Transactional + public void remove(UnitType unitType) { + try { + unitTypeDAO.remove(unitType.getId()); + } catch (InstanceNotFoundException e) { + LOG.error("Trying to remove unit type with id " + unitType.getId() + + " but it is not found in the database",e); + throw new RuntimeException(); + } + } + +} \ No newline at end of file diff --git a/navalplanner-webapp/src/main/webapp/materials/_editUnitType.zul b/navalplanner-webapp/src/main/webapp/materials/_editUnitType.zul new file mode 100644 index 000000000..08b1888c6 --- /dev/null +++ b/navalplanner-webapp/src/main/webapp/materials/_editUnitType.zul @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/navalplanner-webapp/src/main/webapp/materials/unitTypes.zul b/navalplanner-webapp/src/main/webapp/materials/unitTypes.zul new file mode 100644 index 000000000..1be9238c9 --- /dev/null +++ b/navalplanner-webapp/src/main/webapp/materials/unitTypes.zul @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file