Include the cost because of expenses in the WBS imputed hours
pop-up and updates the costs bar in Gantt chart with expenses. FEA: ItEr76S24AdapatingProjectsToExpenses
This commit is contained in:
parent
ed31f2b559
commit
df19131dd4
18 changed files with 570 additions and 50 deletions
|
|
@ -64,4 +64,11 @@ public interface ISumExpensesDAO extends IGenericDAO<SumExpenses, Long> {
|
|||
|
||||
SumExpenses findByOrderElement(OrderElement orderElement);
|
||||
|
||||
/**
|
||||
* Recalculates all the {@link SumExpenses} objets of an {@link Order}.
|
||||
* This is needed when some elements are moved inside the {@link Order}.
|
||||
*
|
||||
* @param orderId
|
||||
*/
|
||||
void recalculateSumExpenses(Long orderId);
|
||||
}
|
||||
|
|
@ -32,12 +32,14 @@ import org.libreplan.business.common.daos.GenericDAOHibernate;
|
|||
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
|
||||
import org.libreplan.business.expensesheet.daos.IExpenseSheetLineDAO;
|
||||
import org.libreplan.business.expensesheet.entities.ExpenseSheetLine;
|
||||
import org.libreplan.business.orders.entities.Order;
|
||||
import org.libreplan.business.orders.entities.OrderElement;
|
||||
import org.libreplan.business.orders.entities.SumExpenses;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Contract for {@link SumExpensesDAO}
|
||||
|
|
@ -197,4 +199,40 @@ public class SumExpensesDAO extends GenericDAOHibernate<SumExpenses, Long> imple
|
|||
.add(Restrictions.eq("orderElement", orderElement)).uniqueResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void recalculateSumExpenses(Long orderId) {
|
||||
try {
|
||||
Order order = orderDAO.find(orderId);
|
||||
resetMapSumExpenses();
|
||||
resetSumExpenses(order);
|
||||
calculateDirectExpenses(order);
|
||||
} catch (InstanceNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetSumExpenses(OrderElement orderElement) {
|
||||
SumExpenses sumExpenses = getByOrderElement(orderElement);
|
||||
if (sumExpenses == null) {
|
||||
sumExpenses = SumExpenses.create(orderElement);
|
||||
}
|
||||
sumExpenses.reset();
|
||||
|
||||
for (OrderElement each : orderElement.getChildren()) {
|
||||
resetSumExpenses(each);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateDirectExpenses(OrderElement orderElement) {
|
||||
for (OrderElement each : orderElement.getChildren()) {
|
||||
calculateDirectExpenses(each);
|
||||
}
|
||||
|
||||
BigDecimal value = BigDecimal.ZERO;
|
||||
for (ExpenseSheetLine line : expenseSheetLineDAO.findByOrderElement(orderElement)) {
|
||||
value = value.add(line.getValue());
|
||||
}
|
||||
addDirectExpenses(orderElement, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.libreplan.business.orders.entities;
|
||||
|
||||
/**
|
||||
* Interface to recalculate {@link SumExpenses} for an {@link Order}.<br />
|
||||
*
|
||||
* This is needed to be called when some elements are moved in the {@link Order}
|
||||
* .
|
||||
*
|
||||
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
|
||||
*/
|
||||
public interface ISumExpensesRecalculator {
|
||||
|
||||
/**
|
||||
* Mark {@link Order} to recalculate {@link SumExpenses}.<br />
|
||||
*
|
||||
* @param orderId
|
||||
*/
|
||||
void recalculate(Long orderId);
|
||||
|
||||
}
|
||||
|
|
@ -117,6 +117,8 @@ public class Order extends OrderLineGroup implements Comparable {
|
|||
|
||||
private boolean neededToRecalculateSumChargedEfforts = false;
|
||||
|
||||
private boolean neededToRecalculateSumExpenses = false;
|
||||
|
||||
public static class CurrentVersionInfo {
|
||||
|
||||
private final OrderVersion orderVersion;
|
||||
|
|
@ -584,4 +586,12 @@ public class Order extends OrderLineGroup implements Comparable {
|
|||
return neededToRecalculateSumChargedEfforts;
|
||||
}
|
||||
|
||||
public void markAsNeededToRecalculateSumExpenses() {
|
||||
neededToRecalculateSumExpenses = true;
|
||||
}
|
||||
|
||||
public boolean isNeededToRecalculateSumExpenses() {
|
||||
return neededToRecalculateSumExpenses;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ public class OrderLineGroup extends OrderElement implements
|
|||
newChild.updateLabels();
|
||||
if (!newChild.isNewObject()) {
|
||||
getOrder().markAsNeededToRecalculateSumChargedEfforts();
|
||||
getOrder().markAsNeededToRecalculateSumExpenses();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -114,6 +115,7 @@ public class OrderLineGroup extends OrderElement implements
|
|||
updateCriterionRequirements();
|
||||
if (!removedChild.isNewObject()) {
|
||||
getOrder().markAsNeededToRecalculateSumChargedEfforts();
|
||||
getOrder().markAsNeededToRecalculateSumExpenses();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.libreplan.business.orders.entities;
|
||||
|
||||
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.expensesheet.entities.ExpenseSheet;
|
||||
import org.libreplan.business.orders.daos.ISumExpensesDAO;
|
||||
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 SumExpenses} for an {@link Order}.<br />
|
||||
*
|
||||
* This is needed to be called when some elements are moved in the {@link Order}
|
||||
* .<br />
|
||||
*
|
||||
* 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
|
||||
* <code>MAX_ATTEMPS_BECAUSE_CONCURRENCY</code> as maximum) till it's performed
|
||||
* without concurrency problems.
|
||||
*
|
||||
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
|
||||
*/
|
||||
@Component
|
||||
@Scope(BeanDefinition.SCOPE_SINGLETON)
|
||||
|
||||
public class SumExpensesRecalculator implements ISumExpensesRecalculator {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(SumExpensesRecalculator.class);
|
||||
|
||||
/**
|
||||
* Number of times that an order is tried to be recalculated if there is any
|
||||
* concurrency issue.<br />
|
||||
*
|
||||
* Concurrency problems could happen because while the recalculation is
|
||||
* being done a {@link ExpenseSheet} is saved with elements in the same
|
||||
* {@link Order}.
|
||||
*/
|
||||
protected static final int MAX_ATTEMPS_BECAUSE_CONCURRENCY = 100;
|
||||
|
||||
@Autowired
|
||||
private ISumExpensesDAO sumExpensesDAO;
|
||||
|
||||
/**
|
||||
* Single thread executor in order to perform the recalculations one by one.
|
||||
*/
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
@Override
|
||||
public void recalculate(Long orderId) {
|
||||
LOG.info("Mark order (id=" + orderId + ") to be recalculated");
|
||||
executor.execute(getRecalculationThread(orderId));
|
||||
}
|
||||
|
||||
private Runnable getRecalculationThread(final Long orderId) {
|
||||
return new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
recalculateSumExpensess(orderId);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void recalculateSumExpensess(Long orderId) throws InterruptedException {
|
||||
recalculateSumExpensess(orderId, 0);
|
||||
}
|
||||
|
||||
private void recalculateSumExpensess(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 + ")");
|
||||
sumExpensesDAO.recalculateSumExpenses(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);
|
||||
|
||||
counter++;
|
||||
recalculateSumExpensess(orderId, counter);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ public interface IMoneyCostCalculator {
|
|||
* {@link OrderElement} to calculate the money cost
|
||||
* @return Money cost of the order element and all its children
|
||||
*/
|
||||
BigDecimal getMoneyCost(OrderElement orderElement);
|
||||
// BigDecimal getMoneyCost(OrderElement orderElement);
|
||||
|
||||
/**
|
||||
* Resets the map used to save cached values of money cost for each
|
||||
|
|
@ -56,4 +56,10 @@ public interface IMoneyCostCalculator {
|
|||
*/
|
||||
void resetMoneyCostMap();
|
||||
|
||||
BigDecimal getCostOfHours(OrderElement orderElement);
|
||||
|
||||
BigDecimal getCostOfExpenses(OrderElement orderElement);
|
||||
|
||||
BigDecimal getMoneyCostTotal(OrderElement orderElement);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,29 +55,75 @@ public class MoneyCostCalculator implements IMoneyCostCalculator {
|
|||
@Autowired
|
||||
private IHourCostDAO hourCostDAO;
|
||||
|
||||
private Map<OrderElement, BigDecimal> moneyCostMap = new HashMap<OrderElement, BigDecimal>();
|
||||
private Map<OrderElement, MoneyCost> moneyCostTotalMap = new HashMap<OrderElement, MoneyCost>();
|
||||
|
||||
public class MoneyCost {
|
||||
private BigDecimal costOfHours;
|
||||
private BigDecimal costOfExpenses;
|
||||
|
||||
public MoneyCost() {
|
||||
}
|
||||
|
||||
public void setCostOfHours(BigDecimal costOfHours) {
|
||||
this.costOfHours = costOfHours;
|
||||
}
|
||||
|
||||
public BigDecimal getCostOfHours() {
|
||||
return costOfHours;
|
||||
}
|
||||
|
||||
public void setCostOfExpenses(BigDecimal costOfExpenses) {
|
||||
this.costOfExpenses = costOfExpenses;
|
||||
}
|
||||
|
||||
public BigDecimal getCostOfExpenses() {
|
||||
return costOfExpenses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetMoneyCostMap() {
|
||||
moneyCostMap = new HashMap<OrderElement, BigDecimal>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getMoneyCost(OrderElement orderElement) {
|
||||
BigDecimal result = moneyCostMap.get(orderElement);
|
||||
if (result != null) {
|
||||
return result;
|
||||
public void resetMoneyCostMap() {
|
||||
moneyCostTotalMap = new HashMap<OrderElement, MoneyCost>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getMoneyCostTotal(OrderElement orderElement) {
|
||||
BigDecimal result = BigDecimal.ZERO.setScale(2);
|
||||
BigDecimal moneyCostOfHours = getCostOfHours(orderElement);
|
||||
if (moneyCostOfHours != null) {
|
||||
result = result.add(moneyCostOfHours);
|
||||
}
|
||||
BigDecimal moneyCostOfExpenses = getCostOfExpenses(orderElement);
|
||||
if (moneyCostOfExpenses != null) {
|
||||
result = result.add(moneyCostOfExpenses).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getCostOfHours(OrderElement orderElement) {
|
||||
MoneyCost moneyCost = moneyCostTotalMap.get(orderElement);
|
||||
if (moneyCost != null) {
|
||||
BigDecimal result = moneyCost.getCostOfHours();
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = BigDecimal.ZERO.setScale(2);
|
||||
BigDecimal result = BigDecimal.ZERO.setScale(2);
|
||||
for (OrderElement each : orderElement.getChildren()) {
|
||||
result = result.add(getMoneyCost(each));
|
||||
result = result.add(getCostOfHours(each));
|
||||
}
|
||||
|
||||
result = result.add(getMoneyCostFromOwnWorkReportLines(orderElement))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
moneyCostMap.put(orderElement, result);
|
||||
if (moneyCost == null) {
|
||||
moneyCost = new MoneyCost();
|
||||
}
|
||||
moneyCost.setCostOfHours(result);
|
||||
moneyCostTotalMap.put(orderElement, moneyCost);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -115,12 +161,36 @@ public class MoneyCostCalculator implements IMoneyCostCalculator {
|
|||
* @param budget
|
||||
* @return A BigDecimal from 0 to 1 with the proportion
|
||||
*/
|
||||
public static BigDecimal getMoneyCostProportion(BigDecimal moneyCost,
|
||||
BigDecimal budget) {
|
||||
public static BigDecimal getMoneyCostProportion(BigDecimal moneyCost, BigDecimal budget) {
|
||||
if (budget.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return moneyCost.divide(budget, 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getCostOfExpenses(OrderElement orderElement) {
|
||||
MoneyCost moneyCost = moneyCostTotalMap.get(orderElement);
|
||||
if (moneyCost != null) {
|
||||
BigDecimal result = moneyCost.getCostOfExpenses();
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal result = BigDecimal.ZERO.setScale(2);
|
||||
if ((orderElement.getSumExpenses()) != null) {
|
||||
result = result.add(orderElement.getSumExpenses().getTotalDirectExpenses());
|
||||
result = result.add(orderElement.getSumExpenses().getTotalIndirectExpenses()).setScale(
|
||||
2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
if (moneyCost == null) {
|
||||
moneyCost = new MoneyCost();
|
||||
}
|
||||
moneyCost.setCostOfExpenses(result);
|
||||
moneyCostTotalMap.put(orderElement, moneyCost);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -310,58 +310,58 @@ public class MoneyCostCalculatorTest {
|
|||
@Test
|
||||
public void basicTest() {
|
||||
givenBasicExample();
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(0)),
|
||||
equalTo(new BigDecimal(500).setScale(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicTestWithoutCostCategoryRelationship() {
|
||||
givenBasicExampleWithoutCostCategoryRelationship();
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(0)),
|
||||
equalTo(new BigDecimal(300).setScale(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exampleOrderLineGroup() {
|
||||
givenExampleOrderLineGroup();
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(0)),
|
||||
equalTo(new BigDecimal(1500).setScale(2)));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(1)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(1)),
|
||||
equalTo(new BigDecimal(500).setScale(2)));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(2)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(2)),
|
||||
equalTo(new BigDecimal(500).setScale(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exampleOrderLineGroupWithDifferentHours1() {
|
||||
givenExampleOrderLineGroupWithDifferentHours(Arrays.asList(0, 10, 5));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(0)),
|
||||
equalTo(new BigDecimal(750).setScale(2)));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(1)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(1)),
|
||||
equalTo(new BigDecimal(500).setScale(2)));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(2)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(2)),
|
||||
equalTo(new BigDecimal(250).setScale(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exampleOrderLineGroupWithDifferentHours2() {
|
||||
givenExampleOrderLineGroupWithDifferentHours(Arrays.asList(6, 0, 0));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(0)),
|
||||
equalTo(new BigDecimal(300).setScale(2)));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(1)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(1)),
|
||||
equalTo(new BigDecimal(0).setScale(2)));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(2)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(2)),
|
||||
equalTo(new BigDecimal(0).setScale(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exampleOrderLineGroupWithDifferentHours3() {
|
||||
givenExampleOrderLineGroupWithDifferentHours(Arrays.asList(6, 5, 10));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(0)),
|
||||
equalTo(new BigDecimal(1050).setScale(2)));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(1)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(1)),
|
||||
equalTo(new BigDecimal(250).setScale(2)));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(2)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(2)),
|
||||
equalTo(new BigDecimal(500).setScale(2)));
|
||||
}
|
||||
|
||||
|
|
@ -370,7 +370,7 @@ public class MoneyCostCalculatorTest {
|
|||
givenExampleWithoutCostCategoryRelationshipButDifferentTypeOfHours(
|
||||
Arrays.asList(10, 5),
|
||||
Arrays.asList(new BigDecimal(30), new BigDecimal(50)));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(0)),
|
||||
equalTo(new BigDecimal(550).setScale(2)));
|
||||
}
|
||||
|
||||
|
|
@ -379,7 +379,7 @@ public class MoneyCostCalculatorTest {
|
|||
givenExampleWithoutCostCategoryRelationshipButDifferentTypeOfHours(
|
||||
Arrays.asList(10, 5, 8), Arrays.asList(new BigDecimal(30),
|
||||
new BigDecimal(50), new BigDecimal(40)));
|
||||
assertThat(moneyCostCalculator.getMoneyCost(orderElements.get(0)),
|
||||
assertThat(moneyCostCalculator.getCostOfHours(orderElements.get(0)),
|
||||
equalTo(new BigDecimal(870).setScale(2)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ package org.libreplan.web.orders;
|
|||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import org.libreplan.business.expensesheet.entities.ExpenseSheetLine;
|
||||
import org.libreplan.business.orders.entities.OrderElement;
|
||||
import org.libreplan.business.reports.dtos.WorkReportLineDTO;
|
||||
import org.libreplan.web.common.Util;
|
||||
|
|
@ -73,6 +74,18 @@ public class AssignedHoursToOrderElementController extends
|
|||
.toFormattedString();
|
||||
}
|
||||
|
||||
public String getTotalDirectExpenses() {
|
||||
return assignedHoursToOrderElementModel.getTotalDirectExpenses();
|
||||
}
|
||||
|
||||
public String getTotalIndirectExpenses() {
|
||||
return assignedHoursToOrderElementModel.getTotalIndirectExpenses();
|
||||
}
|
||||
|
||||
public String getTotalExpenses() {
|
||||
return assignedHoursToOrderElementModel.getTotalExpenses();
|
||||
}
|
||||
|
||||
public String getEffortChildren() {
|
||||
return assignedHoursToOrderElementModel
|
||||
.getAssignedDirectEffortChildren().toFormattedString();
|
||||
|
|
@ -95,6 +108,14 @@ public class AssignedHoursToOrderElementController extends
|
|||
return assignedHoursToOrderElementModel.getMoneyCost();
|
||||
}
|
||||
|
||||
public BigDecimal getCostOfHours() {
|
||||
return assignedHoursToOrderElementModel.getCostOfHours();
|
||||
}
|
||||
|
||||
public BigDecimal getCostOfExpenses() {
|
||||
return assignedHoursToOrderElementModel.getCostOfExpenses();
|
||||
}
|
||||
|
||||
public BigDecimal getMoneyCostPercentage() {
|
||||
return assignedHoursToOrderElementModel.getMoneyCostPercentage();
|
||||
}
|
||||
|
|
@ -159,4 +180,8 @@ public class AssignedHoursToOrderElementController extends
|
|||
}
|
||||
}
|
||||
|
||||
public List<ExpenseSheetLine> getExpenseSheetLines() {
|
||||
return assignedHoursToOrderElementModel.getExpenseSheetLines();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ import java.util.List;
|
|||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.libreplan.business.expensesheet.daos.IExpenseSheetLineDAO;
|
||||
import org.libreplan.business.expensesheet.entities.ExpenseSheetLine;
|
||||
import org.libreplan.business.expensesheet.entities.ExpenseSheetLineComparator;
|
||||
import org.libreplan.business.orders.daos.IOrderElementDAO;
|
||||
import org.libreplan.business.orders.entities.OrderElement;
|
||||
import org.libreplan.business.planner.entities.MoneyCostCalculator;
|
||||
|
|
@ -51,8 +54,10 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
*/
|
||||
@Service
|
||||
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
|
||||
public class AssignedHoursToOrderElementModel implements
|
||||
IAssignedHoursToOrderElementModel {
|
||||
public class AssignedHoursToOrderElementModel implements IAssignedHoursToOrderElementModel {
|
||||
|
||||
@Autowired
|
||||
private final IExpenseSheetLineDAO expenseSheetLineDAO;
|
||||
|
||||
@Autowired
|
||||
private final IWorkReportLineDAO workReportLineDAO;
|
||||
|
|
@ -70,9 +75,12 @@ public class AssignedHoursToOrderElementModel implements
|
|||
private List<WorkReportLineDTO> listWRL;
|
||||
|
||||
@Autowired
|
||||
public AssignedHoursToOrderElementModel(IWorkReportLineDAO workReportLineDAO) {
|
||||
public AssignedHoursToOrderElementModel(IWorkReportLineDAO workReportLineDAO,
|
||||
IExpenseSheetLineDAO expenseSheetLineDAO) {
|
||||
Validate.notNull(workReportLineDAO);
|
||||
Validate.notNull(expenseSheetLineDAO);
|
||||
this.workReportLineDAO = workReportLineDAO;
|
||||
this.expenseSheetLineDAO = expenseSheetLineDAO;
|
||||
this.assignedDirectEffort = EffortDuration.zero();
|
||||
}
|
||||
|
||||
|
|
@ -102,12 +110,12 @@ public class AssignedHoursToOrderElementModel implements
|
|||
private List<WorkReportLineDTO> sortByDate(List<WorkReportLineDTO> listWRL) {
|
||||
Collections.sort(listWRL, new Comparator<WorkReportLineDTO>() {
|
||||
public int compare(WorkReportLineDTO arg0, WorkReportLineDTO arg1) {
|
||||
if (arg0.getDate() == null) {
|
||||
return -1;
|
||||
}
|
||||
if (arg1.getDate() == null) {
|
||||
return 1;
|
||||
}
|
||||
if (arg0.getDate() == null) {
|
||||
return -1;
|
||||
}
|
||||
if (arg1.getDate() == null) {
|
||||
return 1;
|
||||
}
|
||||
return arg0.getDate().compareTo(arg1.getDate());
|
||||
}
|
||||
});
|
||||
|
|
@ -162,6 +170,24 @@ public class AssignedHoursToOrderElementModel implements
|
|||
return this.orderElement.getSumChargedEffort().getTotalChargedEffort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTotalDirectExpenses() {
|
||||
if ((orderElement != null) && (orderElement.getSumExpenses() != null)
|
||||
&& (orderElement.getSumExpenses().getTotalDirectExpenses() != null)) {
|
||||
return orderElement.getSumExpenses().getTotalDirectExpenses().toPlainString();
|
||||
}
|
||||
return BigDecimal.ZERO.toPlainString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTotalIndirectExpenses() {
|
||||
if ((orderElement != null) && (orderElement.getSumExpenses() != null)
|
||||
&& (orderElement.getSumExpenses().getTotalIndirectExpenses() != null)) {
|
||||
return orderElement.getSumExpenses().getTotalIndirectExpenses().toPlainString();
|
||||
}
|
||||
return BigDecimal.ZERO.toPlainString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public EffortDuration getAssignedDirectEffortChildren() {
|
||||
|
|
@ -216,7 +242,41 @@ public class AssignedHoursToOrderElementModel implements
|
|||
if (orderElement == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return moneyCostCalculator.getMoneyCost(orderElement);
|
||||
return moneyCostCalculator.getMoneyCostTotal(orderElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTotalExpenses() {
|
||||
if ((orderElement != null) && (orderElement.getSumExpenses() != null)) {
|
||||
BigDecimal directExpenses = orderElement.getSumExpenses().getTotalDirectExpenses();
|
||||
BigDecimal indirectExpenses = orderElement.getSumExpenses().getTotalIndirectExpenses();
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
if (directExpenses != null) {
|
||||
total = total.add(directExpenses);
|
||||
}
|
||||
if (indirectExpenses != null) {
|
||||
total = total.add(indirectExpenses);
|
||||
}
|
||||
return total.toPlainString();
|
||||
}
|
||||
return BigDecimal.ZERO.toPlainString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getCostOfExpenses() {
|
||||
if (orderElement == null) {
|
||||
return BigDecimal.ZERO.setScale(2);
|
||||
}
|
||||
return moneyCostCalculator.getCostOfExpenses(orderElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public BigDecimal getCostOfHours() {
|
||||
if (orderElement == null) {
|
||||
return BigDecimal.ZERO.setScale(2);
|
||||
}
|
||||
return moneyCostCalculator.getCostOfHours(orderElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -226,8 +286,32 @@ public class AssignedHoursToOrderElementModel implements
|
|||
return BigDecimal.ZERO;
|
||||
}
|
||||
return MoneyCostCalculator.getMoneyCostProportion(
|
||||
moneyCostCalculator.getMoneyCost(orderElement),
|
||||
orderElement.getBudget()).multiply(new BigDecimal(100));
|
||||
moneyCostCalculator.getMoneyCostTotal(orderElement), orderElement.getBudget())
|
||||
.multiply(
|
||||
new BigDecimal(100));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ExpenseSheetLine> getExpenseSheetLines() {
|
||||
if (orderElement != null) {
|
||||
List<ExpenseSheetLine> result = expenseSheetLineDAO.findByOrderElement(orderElement);
|
||||
if (result != null && !result.isEmpty()) {
|
||||
Collections.sort(result, new ExpenseSheetLineComparator());
|
||||
loadDataExpenseSheetLines(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return new ArrayList<ExpenseSheetLine>();
|
||||
}
|
||||
|
||||
private void loadDataExpenseSheetLines(List<ExpenseSheetLine> expenseSheetLineList) {
|
||||
for (ExpenseSheetLine line : expenseSheetLineList) {
|
||||
line.getCode();
|
||||
if (line.getResource() != null) {
|
||||
line.getResource().getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ package org.libreplan.web.orders;
|
|||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import org.libreplan.business.expensesheet.entities.ExpenseSheetLine;
|
||||
import org.libreplan.business.orders.entities.OrderElement;
|
||||
import org.libreplan.business.reports.dtos.WorkReportLineDTO;
|
||||
import org.libreplan.business.workingday.EffortDuration;
|
||||
|
|
@ -51,4 +52,16 @@ public interface IAssignedHoursToOrderElementModel{
|
|||
|
||||
BigDecimal getMoneyCostPercentage();
|
||||
|
||||
public String getTotalDirectExpenses();
|
||||
|
||||
public String getTotalIndirectExpenses();
|
||||
|
||||
public List<ExpenseSheetLine> getExpenseSheetLines();
|
||||
|
||||
public String getTotalExpenses();
|
||||
|
||||
public BigDecimal getCostOfExpenses();
|
||||
|
||||
public BigDecimal getCostOfHours();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -853,6 +853,9 @@ public class OrderModel extends IntegrationEntityModel implements IOrderModel {
|
|||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public boolean hasImputedExpenseSheets(OrderElement order) {
|
||||
if (order.isNewObject()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return orderElementDAO.hasImputedExpenseSheet(order.getId());
|
||||
} catch (InstanceNotFoundException e) {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import java.util.Arrays;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -60,7 +61,6 @@ import org.libreplan.business.common.entities.ProgressType;
|
|||
import org.libreplan.business.externalcompanies.daos.IExternalCompanyDAO;
|
||||
import org.libreplan.business.labels.entities.Label;
|
||||
import org.libreplan.business.orders.daos.IOrderElementDAO;
|
||||
import org.libreplan.business.orders.daos.OrderElementDAO;
|
||||
import org.libreplan.business.orders.entities.Order;
|
||||
import org.libreplan.business.orders.entities.OrderElement;
|
||||
import org.libreplan.business.orders.entities.OrderStatusEnum;
|
||||
|
|
@ -690,9 +690,29 @@ public class TaskElementAdapter {
|
|||
|
||||
@Override
|
||||
public BigDecimal execute() {
|
||||
return moneyCostCalculator
|
||||
.getMoneyCost(taskElement
|
||||
.getOrderElement());
|
||||
return moneyCostCalculator.getMoneyCostTotal(taskElement
|
||||
.getOrderElement());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, BigDecimal> getMoneyItemizedCost() {
|
||||
if ((taskElement == null) || (taskElement.getOrderElement() == null)) {
|
||||
Map<String, BigDecimal> costs = new HashMap<String, BigDecimal>();
|
||||
costs.put("costHours", BigDecimal.ZERO);
|
||||
costs.put("costExpenses", BigDecimal.ZERO);
|
||||
return costs;
|
||||
}
|
||||
return transactionService
|
||||
.runOnReadOnlyTransaction(new IOnTransaction<Map<String, BigDecimal>>() {
|
||||
@Override
|
||||
public Map<String, BigDecimal> execute() {
|
||||
Map<String, BigDecimal> costs = new HashMap<String, BigDecimal>();
|
||||
costs.put("costHours", moneyCostCalculator
|
||||
.getCostOfHours(taskElement.getOrderElement()));
|
||||
costs.put("costExpenses", moneyCostCalculator
|
||||
.getCostOfExpenses(taskElement.getOrderElement()));
|
||||
return costs;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1074,6 +1094,12 @@ public class TaskElementAdapter {
|
|||
getMoneyCostBarPercentage().multiply(
|
||||
new BigDecimal(100)))).append(
|
||||
"<br/>");
|
||||
|
||||
Map<String, BigDecimal> mapCosts = getMoneyItemizedCost();
|
||||
result.append(
|
||||
_("cost because of worked hours: {0}€, cost because of expenses: {1}€",
|
||||
mapCosts.get("costHours"), mapCosts.get("costExpenses")))
|
||||
.append("<br/>");
|
||||
}
|
||||
|
||||
String labels = buildLabelsText();
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import org.libreplan.business.orders.daos.IOrderDAO;
|
|||
import org.libreplan.business.orders.daos.IOrderElementDAO;
|
||||
import org.libreplan.business.orders.entities.HoursGroup;
|
||||
import org.libreplan.business.orders.entities.ISumChargedEffortRecalculator;
|
||||
import org.libreplan.business.orders.entities.ISumExpensesRecalculator;
|
||||
import org.libreplan.business.orders.entities.Order;
|
||||
import org.libreplan.business.orders.entities.OrderElement;
|
||||
import org.libreplan.business.orders.entities.OrderLineGroup;
|
||||
|
|
@ -207,6 +208,9 @@ public class SaveCommandBuilder {
|
|||
@Autowired
|
||||
private ISumChargedEffortRecalculator sumChargedEffortRecalculator;
|
||||
|
||||
@Autowired
|
||||
private ISumExpensesRecalculator sumExpensesRecalculator;
|
||||
|
||||
private class SaveCommand implements ISaveCommand {
|
||||
|
||||
private PlanningState state;
|
||||
|
|
@ -295,6 +299,10 @@ public class SaveCommandBuilder {
|
|||
.getOrder().getId());
|
||||
}
|
||||
|
||||
if (state.getOrder().isNeededToRecalculateSumExpenses()) {
|
||||
sumExpensesRecalculator.recalculate(state.getOrder().getId());
|
||||
}
|
||||
|
||||
fireAfterSave();
|
||||
if (afterSaveActions != null) {
|
||||
afterSaveActions.doActions();
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
<tabs>
|
||||
<tab id="tabDetails" label="${i18n:_('Details')}" selected="true"
|
||||
onSelect="orderElementController.clearAll();"/>
|
||||
<tab id="tabAssignedHours" label="${i18n:_('Imputed hours')}"
|
||||
<tab id="tabAssignedHours" label="${i18n:_('Cost')}"
|
||||
onSelect="orderElementController.setupAssignedHoursToOrderElementController();"/>
|
||||
<tab id="tabAdvances" label="${i18n:_('Progress')}"
|
||||
onSelect="orderElementController.setupManageOrderElementAdvancesController();"/>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
onSelect = "controller.setupOrderElementTreeController();"/>
|
||||
<tab id="tabGeneralData" label="${i18n:_('General data')}" selected="true"
|
||||
onSelect = "controller.setupOrderDetails();"/>
|
||||
<tab id="tabAssignedHours" label="${i18n:_('Imputed hours')}"
|
||||
<tab id="tabAssignedHours" label="${i18n:_('Cost')}"
|
||||
onSelect = "controller.setupAssignedHoursToOrderElementController();"/>
|
||||
<tab id="tabAdvances" label="${i18n:_('Progress')}"
|
||||
onSelect = "controller.setupManageOrderElementAdvancesController();"/>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,55 @@
|
|||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
|
||||
</panelchildren>
|
||||
</panel>
|
||||
<panel title="${i18n:_('Imputed expenses')}">
|
||||
<panelchildren>
|
||||
<separator bar="false" spacing="10px" orient="horizontal"/>
|
||||
<grid id="listExpenses" mold="paging" pageSize="10"
|
||||
model="@{assignedHoursToOrderElementController.expenseSheetLines}" sclass="clickable-rows">
|
||||
<columns sizable="true">
|
||||
<column label="${i18n:_('Date')}" width="120px"/>
|
||||
<column label="${i18n:_('Concept')}"/>
|
||||
<column label="${i18n:_('Resource')}"/>
|
||||
<column label="${i18n:_('Value')}" width="100px"/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row self="@{each='expenseSheetLine'}" value="@{expenseSheetLine}">
|
||||
<label value="@{expenseSheetLine.date, converter='org.libreplan.web.common.typeconverters.LocalDateConverter'}" />
|
||||
<label value="@{expenseSheetLine.concept}"/>
|
||||
<label value="@{expenseSheetLine.resource.getName}"/>
|
||||
<label value="@{expenseSheetLine.value}"/>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
<grid style="border:0px">
|
||||
<columns>
|
||||
<column />
|
||||
<column width="100px" />
|
||||
</columns>
|
||||
<rows>
|
||||
<row style="border-top:1px solid black">
|
||||
<label
|
||||
value="${i18n:_('Sum of direct expenses')}" />
|
||||
<label
|
||||
value="@{assignedHoursToOrderElementController.totalDirectExpenses}" />
|
||||
</row>
|
||||
<row>
|
||||
<label
|
||||
value="${i18n:_('Sum of expenses imputed in children tasks')}" />
|
||||
<label id="totalIDE"
|
||||
value="@{assignedHoursToOrderElementController.totalIndirectExpenses}" />
|
||||
</row>
|
||||
<row
|
||||
style="border-top:1px solid black; font-weight: bold;">
|
||||
<label value="${i18n:_('Total expenses')}"
|
||||
style="font-weight: bold" />
|
||||
<label style="font-weight: bold"
|
||||
value="@{assignedHoursToOrderElementController.totalExpenses}"/>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
</panelchildren>
|
||||
</panel>
|
||||
</vbox>
|
||||
|
|
@ -142,17 +190,37 @@
|
|||
<row>
|
||||
<grid fixedLayout="true">
|
||||
<columns>
|
||||
<column width="20px" />
|
||||
<column width="200px" />
|
||||
<column width="200px" />
|
||||
<column align="right"/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row>
|
||||
<label/>
|
||||
<label
|
||||
value="${i18n:_('Budget in money')}:" />
|
||||
<label
|
||||
value="@{assignedHoursToOrderElementController.budget}" />
|
||||
</row>
|
||||
<row>
|
||||
<detailrow open="true">
|
||||
<grid fixedLayout="true">
|
||||
<columns>
|
||||
<column width="200px" />
|
||||
<column align="right"/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row>
|
||||
<label value="${i18n:_('Because of hours')}:" />
|
||||
<label value="@{assignedHoursToOrderElementController.costOfHours}" />
|
||||
</row>
|
||||
<row>
|
||||
<label value="${i18n:_('Because of expenses')}:" />
|
||||
<label value="@{assignedHoursToOrderElementController.costOfExpenses}" />
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
</detailrow>
|
||||
<label
|
||||
value="${i18n:_('Money spent')}:" />
|
||||
<label
|
||||
|
|
@ -161,6 +229,7 @@
|
|||
</rows>
|
||||
</grid>
|
||||
<hbox>
|
||||
<separator bar="false" spacing="10px" orient="vertical"/>
|
||||
<label
|
||||
value="@{assignedHoursToOrderElementController.moneyCostPercentage}" />
|
||||
<label value="${i18n:_('%')}" />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue