diff --git a/libreplan-business/src/main/java/org/libreplan/business/expensesheet/entities/ExpenseSheet.java b/libreplan-business/src/main/java/org/libreplan/business/expensesheet/entities/ExpenseSheet.java index faa3543ba..dd88474af 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/expensesheet/entities/ExpenseSheet.java +++ b/libreplan-business/src/main/java/org/libreplan/business/expensesheet/entities/ExpenseSheet.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.SortedSet; import java.util.TreeSet; +import org.apache.commons.lang.StringUtils; import org.hibernate.validator.AssertTrue; import org.hibernate.validator.Min; import org.hibernate.validator.NotEmpty; @@ -34,6 +35,7 @@ import org.libreplan.business.common.IHumanIdentifiable; import org.libreplan.business.common.IntegrationEntity; import org.libreplan.business.common.Registry; import org.libreplan.business.common.entities.EntitySequence; +import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.expensesheet.daos.IExpenseSheetDAO; /** @@ -198,4 +200,22 @@ public class ExpenseSheet extends IntegrationEntity implements IHumanIdentifiabl return getCode() + (description != null ? description : ""); } + public ExpenseSheetLine getExpenseSheetLineByCode(String code) + throws InstanceNotFoundException { + + if (StringUtils.isBlank(code)) { + throw new InstanceNotFoundException(code, + ExpenseSheetLine.class.getName()); + } + + for (ExpenseSheetLine l : this.expenseSheetLines) { + if (l.getCode().equalsIgnoreCase(StringUtils.trim(code))) { + return l; + } + } + + throw new InstanceNotFoundException(code, + ExpenseSheetLine.class.getName()); + } + } \ No newline at end of file diff --git a/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/ExpenseSheetDTO.java b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/ExpenseSheetDTO.java new file mode 100644 index 000000000..54762a053 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/ExpenseSheetDTO.java @@ -0,0 +1,69 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 WirelessGalicia, 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 . + */ + +package org.libreplan.ws.expensesheets.api; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +import org.libreplan.business.expensesheet.entities.ExpenseSheet; +import org.libreplan.ws.common.api.IntegrationEntityDTO; + +/** + * DTO for {@link ExpenseSheet} entity. + * + * @author Susana Montes Pedreira + */ +@XmlRootElement(name = "expense-sheet") +public class ExpenseSheetDTO extends IntegrationEntityDTO { + + public final static String ENTITY_TYPE = "expense-sheet"; + + @XmlAttribute + public String description; + + @XmlElementWrapper(name = "expense-sheet-line-list") + @XmlElement(name = "expense-sheet-line") + public List lines = new ArrayList(); + + public ExpenseSheetDTO() { + } + + public ExpenseSheetDTO(String code, String description, + List lines) { + super(code); + this.description = description; + this.lines = lines; + } + + public ExpenseSheetDTO(String description, List lines) { + this(generateCode(), description, lines); + } + + @Override + public String getEntityType() { + return ENTITY_TYPE; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/ExpenseSheetLineDTO.java b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/ExpenseSheetLineDTO.java new file mode 100644 index 000000000..5441bc641 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/ExpenseSheetLineDTO.java @@ -0,0 +1,80 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 WirelessGalicia, 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 . + */ + +package org.libreplan.ws.expensesheets.api; + +import java.math.BigDecimal; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.datatype.XMLGregorianCalendar; + +import org.libreplan.business.expensesheet.entities.ExpenseSheet; +import org.libreplan.ws.common.api.IntegrationEntityDTO; + +/** + * DTO for {@link ExpenseSheet} entity. + * + * @author Susana Montes Pedreira + */ +@XmlRootElement(name = "expense-sheet-line") +public class ExpenseSheetLineDTO extends IntegrationEntityDTO { + + public final static String ENTITY_TYPE = "expense-sheet-line"; + + @XmlAttribute + public String concept; + + @XmlAttribute + public BigDecimal value; + + @XmlAttribute + public String resource; + + @XmlAttribute(name = "task") + public String orderElement; + + @XmlAttribute + public XMLGregorianCalendar date; + + public ExpenseSheetLineDTO() { + } + + public ExpenseSheetLineDTO(String code, String concept, BigDecimal value, String resource, + String orderElement, XMLGregorianCalendar date) { + super(code); + this.concept = concept; + this.value = value; + this.resource = resource; + this.orderElement = orderElement; + this.date = date; + } + + public ExpenseSheetLineDTO(String concept, BigDecimal value, String resource, + String orderElement, + XMLGregorianCalendar date) { + this(generateCode(), concept, value, resource, orderElement, date); + } + + @Override + public String getEntityType() { + return ENTITY_TYPE; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/ExpenseSheetListDTO.java b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/ExpenseSheetListDTO.java new file mode 100644 index 000000000..525cd1ade --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/ExpenseSheetListDTO.java @@ -0,0 +1,45 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012, WirelessGalicia 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 . + */ + +package org.libreplan.ws.expensesheets.api; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * DTO for a list of {@link ExpensesSheet} entities. + * + * @author Susana Montes Pedreira + */ +@XmlRootElement(name = "expense-sheet-list") +public class ExpenseSheetListDTO { + + @XmlElement(name = "expense-sheet") + public List expenseSheets = new ArrayList(); + + public ExpenseSheetListDTO() { + } + + public ExpenseSheetListDTO(List expenseSheets) { + this.expenseSheets = expenseSheets; + } +} \ No newline at end of file diff --git a/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/IExpenseSheetService.java b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/IExpenseSheetService.java new file mode 100644 index 000000000..31ab45013 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/api/IExpenseSheetService.java @@ -0,0 +1,41 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012, WirelessGalicia 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 . + */ + +package org.libreplan.ws.expensesheets.api; + +import javax.ws.rs.core.Response; + +import org.libreplan.business.expensesheet.entities.ExpenseSheet; +import org.libreplan.ws.common.api.InstanceConstraintViolationsListDTO; + +/** + * Service for managing {@link ExpenseSheet} entities. + * + * @author Susana Montes Pedreira + */ +public interface IExpenseSheetService { + + ExpenseSheetListDTO getExpenseSheets(); + + InstanceConstraintViolationsListDTO addExpenseSheets( + ExpenseSheetListDTO expenseSheetListDTO); + + Response getLabel(String code); + +} \ No newline at end of file diff --git a/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/impl/ExpenseSheetConverter.java b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/impl/ExpenseSheetConverter.java new file mode 100644 index 000000000..37829a48d --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/impl/ExpenseSheetConverter.java @@ -0,0 +1,264 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 WirelessGalicia, 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 . + */ + +package org.libreplan.ws.expensesheets.impl; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.xml.datatype.XMLGregorianCalendar; + +import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; +import org.libreplan.business.common.Registry; +import org.libreplan.business.common.exceptions.InstanceNotFoundException; +import org.libreplan.business.common.exceptions.ValidationException; +import org.libreplan.business.expensesheet.entities.ExpenseSheet; +import org.libreplan.business.expensesheet.entities.ExpenseSheetLine; +import org.libreplan.business.orders.entities.OrderElement; +import org.libreplan.business.resources.entities.Resource; +import org.libreplan.ws.common.impl.DateConverter; +import org.libreplan.ws.expensesheets.api.ExpenseSheetDTO; +import org.libreplan.ws.expensesheets.api.ExpenseSheetLineDTO; +import org.libreplan.ws.expensesheets.api.ExpenseSheetListDTO; + +/** + * Converter from/to {@link ExpenseSheet} related entities to/from DTOs. + * + * @author Susana Montes Pedreira + */ +public final class ExpenseSheetConverter { + + private ExpenseSheetConverter() { + } + + public final static ExpenseSheetListDTO toDTO(Collection expenseSheets) { + List expenseSheetDTOs = new ArrayList(); + + for (ExpenseSheet expenseSheet : expenseSheets) { + expenseSheetDTOs.add(toDTO(expenseSheet)); + } + + return new ExpenseSheetListDTO(expenseSheetDTOs); + } + + public final static ExpenseSheetDTO toDTO(ExpenseSheet expenseSheet) { + String code = expenseSheet.getCode(); + if (StringUtils.isBlank(code)) { + throw new ValidationException("missing code in the expense sheet"); + } + + List lineDTOs = new ArrayList(); + + if (expenseSheet.getExpenseSheetLines() == null + || expenseSheet.getExpenseSheetLines().isEmpty()) { + throw new ValidationException( + "the expense sheet must have least a expense sheet line."); + } + + for (ExpenseSheetLine line : expenseSheet.getExpenseSheetLines()) { + lineDTOs.add(toDTO(line)); + } + + return new ExpenseSheetDTO(code, expenseSheet.getDescription(), + lineDTOs); + } + + private static ExpenseSheetLineDTO toDTO(ExpenseSheetLine line) { + if (line != null) { + + String code = line.getCode(); + if (StringUtils.isBlank(code)) { + throw new ValidationException( + "missing code in the expense sheet line"); + } + + BigDecimal value = line.getValue(); + if (value != null && value.compareTo(BigDecimal.ZERO) > 0) { + + } + String resourceCode = null; + if (line.getResource() != null) { + resourceCode = line.getResource().getCode(); + } + + String orderElementCode = null; + if (line.getOrderElement() != null) { + orderElementCode = line.getOrderElement().getCode(); + } else { + throw new ValidationException( + "missing order element code in a expense sheet line"); + } + + XMLGregorianCalendar date = null; + if (line.getDate() != null) { + date = DateConverter.toXMLGregorianCalendar(line.getDate()); + } else { + throw new ValidationException( + "missing date in a expense sheet line"); + } + + return new ExpenseSheetLineDTO(code, line.getConcept(), value, + resourceCode, orderElementCode, date); + } else { + throw new ValidationException( + "the expense sheet line is not initialized"); + } + } + + public final static ExpenseSheet toEntity(ExpenseSheetDTO expenseSheetDTO) { + ExpenseSheet expenseSheet = ExpenseSheet.create(); + expenseSheet.setCode(expenseSheetDTO.code); + expenseSheet.setDescription(expenseSheetDTO.description); + + for (ExpenseSheetLineDTO lineDTO : expenseSheetDTO.lines) { + expenseSheet.add(toEntity(lineDTO, expenseSheet)); + } + + return expenseSheet; + } + + private static ExpenseSheetLine toEntity(ExpenseSheetLineDTO lineDTO, ExpenseSheet expenseSheet) { + String code = lineDTO.code; + if(StringUtils.isBlank(code)){ + throw new ValidationException("missing code expense sheet line"); + } + + BigDecimal value = lineDTO.value; + String concept = lineDTO.concept; + + LocalDate date = null; + if (lineDTO.date != null) { + date = DateConverter.toLocalDate(lineDTO.date); + } + + String orderElementCode = lineDTO.orderElement; + OrderElement orderElement = null; + try{ + orderElement = Registry.getOrderElementDAO().findByCode( + orderElementCode); + }catch (InstanceNotFoundException e) { + throw new ValidationException( + "There is no order element with this code"); + } + + ExpenseSheetLine line = ExpenseSheetLine.create(value, concept, date, + orderElement); + line.setExpenseSheet(expenseSheet); + line.setCode(code); + + if(lineDTO.resource != null){ + String resourceCode = lineDTO.resource; + try{ + Resource resource = Registry.getResourceDAO().findByCode(resourceCode); + line.setResource(resource); + }catch (InstanceNotFoundException e) { + throw new ValidationException( + "There is no resource with this code"); + } + } + + return line; + } + + public final static void updateExpenseSheet(ExpenseSheet expenseSheet, + ExpenseSheetDTO expenseSheetDTO) throws ValidationException { + + if (StringUtils.isBlank(expenseSheetDTO.code)) { + throw new ValidationException("missing code in a expense sheet."); + } + + if (!StringUtils.isBlank(expenseSheetDTO.description)) { + expenseSheet.setDescription(expenseSheetDTO.description); + } + /* + * 1: Update the existing expense sheet line or add new expense sheet + * line. + */ + for (ExpenseSheetLineDTO lineDTO : expenseSheetDTO.lines) { + + /* Step 1.1: requires each expense sheet line DTO to have a code. */ + if (StringUtils.isBlank(lineDTO.code)) { + throw new ValidationException( + "missing code in a expense sheet line"); + } + + try { + ExpenseSheetLine line = expenseSheet + .getExpenseSheetLineByCode(lineDTO.code); + updateExpenseSheetLine(line, lineDTO); + } catch (InstanceNotFoundException e) { + expenseSheet.add(toEntity(lineDTO, expenseSheet)); + } + } + } + + public final static void updateExpenseSheetLine(ExpenseSheetLine line, + ExpenseSheetLineDTO lineDTO) throws ValidationException { + + /* + * 1: Update the concept + */ + if (lineDTO.concept != null) { + line.setConcept(lineDTO.concept); + } + /* + * 2: Update the value + */ + if (lineDTO.value != null) { + line.setValue(lineDTO.value); + } + + /* + * 3: Update the order element + */ + String orderElementCode = lineDTO.orderElement; + if (!StringUtils.isBlank(orderElementCode)) { + try { + OrderElement orderElement = Registry.getOrderElementDAO() + .findUniqueByCode(orderElementCode); + line.setOrderElement(orderElement); + } catch (InstanceNotFoundException e) { + throw new ValidationException("There is no task with this code"); + } + } + + /* Step 3.1: Update the date. */ + if (lineDTO != null) { + LocalDate date = DateConverter.toLocalDate(lineDTO.date); + line.setDate(date); + } + + /* Step 3.4: Update the resource. */ + if (lineDTO.resource != null) { + try { + Resource resource = Registry.getResourceDAO().findByCode( + lineDTO.resource); + line.setResource(resource); + } catch (InstanceNotFoundException e) { + throw new ValidationException( + "There is no resource with this code"); + } + } + + } + +} \ No newline at end of file diff --git a/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/impl/ExpenseSheetServiceREST.java b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/impl/ExpenseSheetServiceREST.java new file mode 100644 index 000000000..f61a57cd9 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/ws/expensesheets/impl/ExpenseSheetServiceREST.java @@ -0,0 +1,163 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 WirelessGalicia, 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 . + */ + +package org.libreplan.ws.expensesheets.impl; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +import org.libreplan.business.common.IOnTransaction; +import org.libreplan.business.common.daos.IIntegrationEntityDAO; +import org.libreplan.business.common.exceptions.InstanceNotFoundException; +import org.libreplan.business.common.exceptions.ValidationException; +import org.libreplan.business.expensesheet.daos.IExpenseSheetDAO; +import org.libreplan.business.expensesheet.entities.ExpenseSheet; +import org.libreplan.business.orders.daos.ISumExpensesDAO; +import org.libreplan.ws.common.api.InstanceConstraintViolationsListDTO; +import org.libreplan.ws.common.impl.GenericRESTService; +import org.libreplan.ws.common.impl.RecoverableErrorException; +import org.libreplan.ws.expensesheets.api.ExpenseSheetDTO; +import org.libreplan.ws.expensesheets.api.ExpenseSheetListDTO; +import org.libreplan.ws.expensesheets.api.IExpenseSheetService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * REST-based implementation of {@link IExpenseSheetService}. + * + * @author Susana Montes Pedreira + */ +@Path("/expenses/") +@Produces("application/xml") +@Service("expenseSheetServiceREST") +public class ExpenseSheetServiceREST extends + GenericRESTService implements + IExpenseSheetService { + + @Autowired + private ISumExpensesDAO sumExpensesDAO; + + @Autowired + private IExpenseSheetDAO expenseSheetDAO; + + /** + * It saves (inserts or updates) an entity DTO by using a new transaction. + * + * @throws ValidationException if validations are not passed + * @throws RecoverableErrorException if a recoverable error occurs + * @author Susana Montes Pedreira + * + */ + protected void insertOrUpdate(final ExpenseSheetDTO entityDTO) + throws ValidationException, RecoverableErrorException { + /* + * NOTE: ValidationException and RecoverableErrorException are runtime + * exceptions. In consequence, if any of them occurs, transaction is + * automatically rolled back. + */ + + IOnTransaction save = new IOnTransaction() { + + @Override + public Void execute() { + + ExpenseSheet entity = null; + IIntegrationEntityDAO entityDAO = getIntegrationEntityDAO(); + + /* Insert or update? */ + try { + entity = entityDAO.findByCode(entityDTO.code); + updateEntity(entity, entityDTO); + } catch (InstanceNotFoundException e) { + entity = toEntity(entityDTO); + } + + /* + * Validate and save (insert or update) the entity. + */ + entity.validate(); + sumExpensesDAO + .updateRelatedSumExpensesWithExpenseSheetLineSet(entity + .getExpenseSheetLines()); + entity.updateCalculatedProperties(); + + entityDAO.saveWithoutValidating(entity); + + return null; + + } + + }; + + transactionService.runOnAnotherTransaction(save); + } + + @Override + @POST + @Consumes("application/xml") + @Transactional + public InstanceConstraintViolationsListDTO addExpenseSheets( + ExpenseSheetListDTO expenseSheetListDTO) { + return save(expenseSheetListDTO.expenseSheets); + } + + @Override + protected IIntegrationEntityDAO getIntegrationEntityDAO() { + return expenseSheetDAO; + } + + @Override + protected ExpenseSheetDTO toDTO(ExpenseSheet entity) { + return ExpenseSheetConverter.toDTO(entity); + } + + @Override + protected ExpenseSheet toEntity(ExpenseSheetDTO entityDTO) + throws ValidationException, RecoverableErrorException { + return ExpenseSheetConverter.toEntity(entityDTO); + } + + @Override + @GET + @Path("/{code}/") + @Transactional(readOnly = true) + public Response getLabel(@PathParam("code") String code) { + return getDTOByCode(code); + } + + @Override + protected void updateEntity(ExpenseSheet entity, ExpenseSheetDTO entityDTO) + throws ValidationException, RecoverableErrorException { + ExpenseSheetConverter.updateExpenseSheet(entity, entityDTO); + + } + + @Override + @GET + @Transactional(readOnly = true) + public ExpenseSheetListDTO getExpenseSheets() { + return new ExpenseSheetListDTO(findAll()); + } +} diff --git a/libreplan-webapp/src/main/resources/libreplan-webapp-spring-config.xml b/libreplan-webapp/src/main/resources/libreplan-webapp-spring-config.xml index 42a3cbd6e..b04e89a33 100644 --- a/libreplan-webapp/src/main/resources/libreplan-webapp-spring-config.xml +++ b/libreplan-webapp/src/main/resources/libreplan-webapp-spring-config.xml @@ -72,6 +72,7 @@ +