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:
Susana Montes Pedreira 2012-05-07 08:03:30 +01:00
parent ed31f2b559
commit df19131dd4
18 changed files with 570 additions and 50 deletions

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
};
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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)));
}

View file

@ -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();
}
}

View file

@ -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();
}
}
}
}

View file

@ -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();
}

View file

@ -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) {

View file

@ -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();

View file

@ -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();

View file

@ -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();"/>

View file

@ -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();"/>

View file

@ -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:_('%')}" />