diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/ISumChargedEffortRecalculator.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/ISumChargedEffortRecalculator.java
new file mode 100644
index 000000000..7db66e04c
--- /dev/null
+++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/ISumChargedEffortRecalculator.java
@@ -0,0 +1,44 @@
+/*
+ * This file is part of LibrePlan
+ *
+ * Copyright (C) 2012 Igalia, 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.business.orders.entities;
+
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Interface to recalculate {@link SumChargedEffort} for an {@link Order}.
+ *
+ * This is needed to be called when some elements are moved in the {@link Order}
+ * .
+ *
+ * @author Manuel Rego Casasnovas
+ */
+public interface ISumChargedEffortRecalculator {
+
+ /**
+ * Mark {@link Order} to recalculate {@link SumChargedEffort}.
+ *
+ * It adds the orderId to a {@link BlockingQueue} that will be
+ * read by a thread in charge of perform the recalculations.
+ *
+ * @param orderId
+ */
+ void recalculate(Long orderId);
+
+}
diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffortRecalculator.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffortRecalculator.java
new file mode 100644
index 000000000..663ccfff1
--- /dev/null
+++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffortRecalculator.java
@@ -0,0 +1,140 @@
+/*
+ * This file is part of LibrePlan
+ *
+ * Copyright (C) 2012 Igalia, 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.business.orders.entities;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.libreplan.business.orders.daos.ISumChargedEffortDAO;
+import org.libreplan.business.workreports.entities.WorkReport;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.Scope;
+import org.springframework.dao.OptimisticLockingFailureException;
+import org.springframework.stereotype.Component;
+
+/**
+ * Class to recalculate {@link SumChargedEffort} for an {@link Order}.
+ *
+ * This is needed to be called when some elements are moved in the {@link Order}
+ * .
+ *
+ * This class uses a thread, in order to call one by one all the requests
+ * received. Moreover, if there's any concurrency issue (because of some reports
+ * were saving in the meanwhile) the recalculation is repeated again (with
+ * MAX_ATTEMPS_BECAUSE_CONCURRENCY as maximum) till it's performed
+ * without concurrency problems.
+ *
+ * @author Manuel Rego Casasnovas
+ */
+@Component
+@Scope(BeanDefinition.SCOPE_SINGLETON)
+public class SumChargedEffortRecalculator implements
+ ISumChargedEffortRecalculator {
+
+ private static final Log LOG = LogFactory
+ .getLog(SumChargedEffortRecalculator.class);
+
+ /**
+ * Number of times that an order is tried to be recalculated if there is any
+ * concurrency issue.
+ *
+ * Concurrency problems could happen because while the recalculation is
+ * being done a {@link WorkReport} is saved with elements in the same
+ * {@link Order}.
+ */
+ protected static final int MAX_ATTEMPS_BECAUSE_CONCURRENCY = 100;
+
+ @Autowired
+ private ISumChargedEffortDAO sumChargedEffortDAO;
+
+ /**
+ * Queue to store the id of the {@link Order} to be recalculated.
+ */
+ private BlockingQueue queue = new ArrayBlockingQueue(1);
+
+ /**
+ * The constructor launch the thread, that will be waiting for elements in
+ * the queue in order to perform the recalculations.
+ *
+ * The class is instantiated by Spring and it is a singleton, so you don't
+ * need to worry about calling this constructor. This will be done by Spring
+ * while launching the application.
+ */
+ public SumChargedEffortRecalculator() {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ executor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ LOG.info("Waiting for orders to recalculate from queue");
+ Long orderId = queue.take();
+ recalculateSumChargedEfforts(orderId);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private void recalculateSumChargedEfforts(Long orderId)
+ throws InterruptedException {
+ recalculateSumChargedEfforts(orderId, 0);
+ }
+
+ private void recalculateSumChargedEfforts(Long orderId, int counter)
+ throws InterruptedException {
+ if (counter > MAX_ATTEMPS_BECAUSE_CONCURRENCY) {
+ LOG.error("Impossible to recalculate order (id=" + orderId
+ + ") due to concurrency problems");
+ return;
+ }
+
+ try {
+ LOG.info("Recalculate order (id=" + orderId + ")");
+ sumChargedEffortDAO.recalculateSumChargedEfforts(orderId);
+ } catch (OptimisticLockingFailureException e) {
+ // Wait 1 second and try again
+ LOG.info("Concurrency problem recalculating order (id="
+ + orderId + ") trying again in 1 second (attempt "
+ + counter + ")");
+ Thread.sleep(1000);
+ recalculateSumChargedEfforts(orderId, counter++);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void recalculate(Long orderId) {
+ try {
+ queue.put(orderId);
+ LOG.info("Add order (id=" + orderId + ") to recalculate in queue");
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java
index 1873c7635..fb720f09f 100644
--- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java
+++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java
@@ -51,8 +51,8 @@ import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.orders.daos.IOrderDAO;
import org.libreplan.business.orders.daos.IOrderElementDAO;
-import org.libreplan.business.orders.daos.ISumChargedEffortDAO;
import org.libreplan.business.orders.entities.HoursGroup;
+import org.libreplan.business.orders.entities.ISumChargedEffortRecalculator;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLineGroup;
@@ -205,7 +205,7 @@ public class SaveCommandBuilder {
private IDependencyDAO dependencyDAO;
@Autowired
- private ISumChargedEffortDAO sumChargedEffortDAO;
+ private ISumChargedEffortRecalculator sumChargedEffortRecalculator;
private class SaveCommand implements ISaveCommand {
@@ -291,7 +291,7 @@ public class SaveCommandBuilder {
if (state.getOrder()
.isNeededToRecalculateSumChargedEfforts()) {
- sumChargedEffortDAO.recalculateSumChargedEfforts(state
+ sumChargedEffortRecalculator.recalculate(state
.getOrder().getId());
}