From 213e68c36e951d9916b562a54cacc37521b21853 Mon Sep 17 00:00:00 2001 From: Manuel Rego Casasnovas Date: Mon, 19 Mar 2012 11:22:14 +0100 Subject: [PATCH] Implement money cost calculation in a new class called MoneyCostCalculator Add a basic test to check that everything is working as expected FEA: ItEr76S17MoneyCostMonitoringSystem --- .../costcategories/daos/HourCostDAO.java | 31 ++- .../costcategories/daos/IHourCostDAO.java | 30 ++- .../entities/IMoneyCostCalculator.java | 49 +++++ .../planner/entities/MoneyCostCalculator.java | 75 +++++++ .../entities/MoneyCostCalculatorTest.java | 189 ++++++++++++++++++ 5 files changed, 369 insertions(+), 5 deletions(-) create mode 100644 libreplan-business/src/main/java/org/libreplan/business/planner/entities/IMoneyCostCalculator.java create mode 100644 libreplan-business/src/main/java/org/libreplan/business/planner/entities/MoneyCostCalculator.java create mode 100644 libreplan-business/src/test/java/org/libreplan/business/test/planner/entities/MoneyCostCalculatorTest.java diff --git a/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/HourCostDAO.java b/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/HourCostDAO.java index 0de44e132..7a576a373 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/HourCostDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/HourCostDAO.java @@ -3,7 +3,7 @@ * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia - * Copyright (C) 2010-2011 Igalia, S.L. + * Copyright (C) 2010-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 @@ -21,20 +21,24 @@ package org.libreplan.business.costcategories.daos; -import java.util.Collection; +import java.math.BigDecimal; -import org.hibernate.criterion.Restrictions; +import org.hibernate.Query; +import org.joda.time.LocalDate; import org.libreplan.business.common.daos.IntegrationEntityDAO; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.costcategories.entities.HourCost; import org.libreplan.business.costcategories.entities.TypeOfWorkHours; +import org.libreplan.business.resources.entities.Resource; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; /** * @author Jacobo Aragunde Perez * @author Diego Pino García + * @author Manuel Rego Casasnovas */ @Repository @Scope(BeanDefinition.SCOPE_SINGLETON) @@ -53,4 +57,25 @@ public class HourCostDAO extends IntegrationEntityDAO implements super.remove(id); } + @Override + @Transactional(readOnly = true) + public BigDecimal getPriceCostFromResourceDateAndType(Resource resource, + LocalDate date, TypeOfWorkHours type) { + String strQuery = "SELECT hc.priceCost " + + "FROM ResourcesCostCategoryAssignment rcca, HourCost hc " + + "WHERE rcca.costCategory = hc.category " + + "AND rcca.resource = :resource " + "AND hc.type = :type " + + "AND rcca.initDate <= :date " + + "AND (rcca.endDate >= :date OR rcca.endDate IS NULL) " + + "AND hc.initDate <= :date " + + "AND (hc.endDate >= :date OR hc.endDate IS NULL)"; + + Query query = getSession().createQuery(strQuery); + query.setParameter("resource", resource); + query.setParameter("date", date); + query.setParameter("type", type); + + return (BigDecimal) query.uniqueResult(); + } + } \ No newline at end of file diff --git a/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/IHourCostDAO.java b/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/IHourCostDAO.java index 659753c3f..e631d9636 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/IHourCostDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/IHourCostDAO.java @@ -3,7 +3,7 @@ * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia - * Copyright (C) 2010-2011 Igalia, S.L. + * Copyright (C) 2010-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 @@ -21,17 +21,43 @@ package org.libreplan.business.costcategories.daos; +import java.math.BigDecimal; + +import org.joda.time.LocalDate; import org.libreplan.business.common.daos.IIntegrationEntityDAO; import org.libreplan.business.common.exceptions.InstanceNotFoundException; +import org.libreplan.business.costcategories.entities.CostCategory; import org.libreplan.business.costcategories.entities.HourCost; +import org.libreplan.business.costcategories.entities.ResourcesCostCategoryAssignment; +import org.libreplan.business.costcategories.entities.TypeOfWorkHours; +import org.libreplan.business.resources.entities.Resource; /** * @author Jacobo Aragunde Perez * @author Diego Pino García + * @author Manuel Rego Casasnovas */ public interface IHourCostDAO extends IIntegrationEntityDAO { @Override public void remove(Long id) throws InstanceNotFoundException; -} \ No newline at end of file + /** + * Returns the price cost of a {@link HourCost} associated with a + * {@link Resource} in a specific {@link LocalDate} and with a concrete + * {@link TypeOfWorkHours}
+ * + * The association is done through {@link CostCategory} associated to the + * resource in the specific date (from class + * {@link ResourcesCostCategoryAssignment}). + * + * @param resource + * @param date + * @param type + * @return A {@link BigDecimal} with the price cost for a {@link Resource} + * in a {@link LocalDate} for this {@link TypeOfWorkHours} + */ + BigDecimal getPriceCostFromResourceDateAndType(Resource resource, + LocalDate date, TypeOfWorkHours type); + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/IMoneyCostCalculator.java b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/IMoneyCostCalculator.java new file mode 100644 index 000000000..594a51471 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/IMoneyCostCalculator.java @@ -0,0 +1,49 @@ +/* + * 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.planner.entities; + +import java.math.BigDecimal; + +import org.libreplan.business.orders.entities.OrderElement; + +/** + * Interface to calculate the money cost of a {@link TaskElement}. + * + * @author Manuel Rego Casasnovas + */ +public interface IMoneyCostCalculator { + + /** + * Returns the money cost of a {@link TaskElement}.
+ * + * It uses the {@link OrderElement} (or OrderElements) associated to the + * {@link TaskElement} in order to calculate the cost using the following + * formula:
+ * Sum of all the hours devoted to a task multiplied by the cost of + * each hour according to these parameters (type of hour, cost category of + * the resource, date of the work report) + * + * @param The + * {@link TaskElement} to calculate the money cost + * @return Money cost of the task + */ + BigDecimal getMoneyCost(TaskElement taskElement); + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/planner/entities/MoneyCostCalculator.java b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/MoneyCostCalculator.java new file mode 100644 index 000000000..d59e16b5d --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/planner/entities/MoneyCostCalculator.java @@ -0,0 +1,75 @@ +/* + * 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.planner.entities; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; + +import org.libreplan.business.costcategories.daos.IHourCostDAO; +import org.libreplan.business.orders.entities.OrderElement; +import org.libreplan.business.workreports.daos.IWorkReportLineDAO; +import org.libreplan.business.workreports.entities.WorkReportLine; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * Class to calculate the money cost of a {@link TaskElement}. + * + * Money cost is calculated checking the hours reported for that task and using + * the cost category of each resource in the different dates. + * + * @author Manuel Rego Casasnovas + */ +@Component +@Scope(BeanDefinition.SCOPE_SINGLETON) +public class MoneyCostCalculator implements IMoneyCostCalculator { + + @Autowired + private IWorkReportLineDAO workReportLineDAO; + + @Autowired + private IHourCostDAO hourCostDAO; + + @Override + public BigDecimal getMoneyCost(TaskElement taskElement) { + OrderElement orderElement = taskElement.getOrderElement(); + + List workReportLines = workReportLineDAO + .findByOrderElementAndChildren(orderElement, false); + + BigDecimal result = BigDecimal.ZERO.setScale(2); + for (WorkReportLine workReportLine : workReportLines) { + BigDecimal priceCost = hourCostDAO + .getPriceCostFromResourceDateAndType( + workReportLine.getResource(), + workReportLine.getLocalDate(), + workReportLine.getTypeOfWorkHours()); + BigDecimal cost = priceCost.multiply(workReportLine.getEffort() + .toHoursAsDecimalWithScale(2)); + result = result.add(cost); + } + + return result.setScale(2, RoundingMode.HALF_UP); + } + +} diff --git a/libreplan-business/src/test/java/org/libreplan/business/test/planner/entities/MoneyCostCalculatorTest.java b/libreplan-business/src/test/java/org/libreplan/business/test/planner/entities/MoneyCostCalculatorTest.java new file mode 100644 index 000000000..7227fede8 --- /dev/null +++ b/libreplan-business/src/test/java/org/libreplan/business/test/planner/entities/MoneyCostCalculatorTest.java @@ -0,0 +1,189 @@ +/* + * 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.test.planner.entities; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createNiceMock; +import static org.easymock.classextension.EasyMock.replay; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.libreplan.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE; +import static org.libreplan.business.test.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_TEST_FILE; + +import java.math.BigDecimal; +import java.util.Date; + +import org.joda.time.LocalDate; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.libreplan.business.common.exceptions.InstanceNotFoundException; +import org.libreplan.business.costcategories.daos.ICostCategoryDAO; +import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO; +import org.libreplan.business.costcategories.entities.CostCategory; +import org.libreplan.business.costcategories.entities.HourCost; +import org.libreplan.business.costcategories.entities.ResourcesCostCategoryAssignment; +import org.libreplan.business.costcategories.entities.TypeOfWorkHours; +import org.libreplan.business.orders.daos.IOrderElementDAO; +import org.libreplan.business.orders.entities.OrderElement; +import org.libreplan.business.orders.entities.OrderLine; +import org.libreplan.business.planner.entities.IMoneyCostCalculator; +import org.libreplan.business.planner.entities.MoneyCostCalculator; +import org.libreplan.business.planner.entities.TaskElement; +import org.libreplan.business.resources.daos.IResourceDAO; +import org.libreplan.business.resources.entities.Resource; +import org.libreplan.business.resources.entities.Worker; +import org.libreplan.business.workingday.EffortDuration; +import org.libreplan.business.workreports.daos.IWorkReportDAO; +import org.libreplan.business.workreports.daos.IWorkReportTypeDAO; +import org.libreplan.business.workreports.entities.WorkReport; +import org.libreplan.business.workreports.entities.WorkReportLine; +import org.libreplan.business.workreports.entities.WorkReportType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +/** + * Test for {@link MoneyCostCalculator} + * + * @author Manuel Rego Casasnovas + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { BUSINESS_SPRING_CONFIG_FILE, + BUSINESS_SPRING_CONFIG_TEST_FILE }) +@Transactional +public class MoneyCostCalculatorTest { + + @Autowired + private IMoneyCostCalculator moneyCostCalculator; + + @Autowired + private IOrderElementDAO orderElementDAO; + + @Autowired + private IResourceDAO resourceDAO; + + @Autowired + private IWorkReportDAO workReportDAO; + + @Autowired + private IWorkReportTypeDAO workReportTypeDAO; + + @Autowired + private ICostCategoryDAO costCategoryDAO; + + @Autowired + private ITypeOfWorkHoursDAO typeOfWorkHoursDAO; + + private TypeOfWorkHours typeOfWorkHours; + private CostCategory costCategory; + private Resource resource; + private OrderElement orderElement; + private WorkReportType workReportType; + private WorkReport workReport; + private TaskElement taskElement; + + private void givenTypeOfWorkHours() { + typeOfWorkHours = TypeOfWorkHours.createUnvalidated( + "default-type-of-work-hours", "default-type-of-work-hours", + true, new BigDecimal(30)); + typeOfWorkHoursDAO.save(typeOfWorkHours); + } + + private void givenCostCategory() { + costCategory = CostCategory.createUnvalidated("default-cost-category", + "default-cost-category", true); + HourCost hourCost = HourCost.createUnvalidated( + "default-hour-cost", new BigDecimal(50), new LocalDate()); + hourCost.setType(typeOfWorkHours); + costCategory.addHourCost(hourCost); + costCategoryDAO.save(costCategory); + } + + private void givenResource() { + resource = Worker.createUnvalidated("default-resource", + "default-resource", "default-resource", "default-resource"); + + ResourcesCostCategoryAssignment resourcesCostCategoryAssignment = ResourcesCostCategoryAssignment + .create(); + resourcesCostCategoryAssignment + .setCode("resources-cost-category-assignment"); + resourcesCostCategoryAssignment.setCostCategory(costCategory); + resourcesCostCategoryAssignment.setInitDate(new LocalDate()); + + resource.addResourcesCostCategoryAssignment(resourcesCostCategoryAssignment); + resourceDAO.save(resource); + } + + private void givenOrderElement() { + orderElement = OrderLine + .createOrderLineWithUnfixedPercentage(100); + orderElement.setCode("default-order-element"); + orderElement.setName("default-order-element"); + orderElement.getHoursGroups().get(0).setCode("default-hours-group"); + orderElementDAO.save(orderElement); + } + + private void giveWorkReportType() { + workReportType = WorkReportType.create("default-work-report-type", + "default-work-report-type"); + workReportTypeDAO.save(workReportType); + } + + private void givenWorkReport() { + workReport = WorkReport.create(workReportType); + workReport.setCode("default-work-report"); + + WorkReportLine workReportLine = WorkReportLine.create(workReport); + workReportLine.setCode("default-work-report-line"); + workReportLine.setDate(new Date()); + workReportLine.setResource(resource); + workReportLine.setOrderElement(orderElement); + workReportLine.setTypeOfWorkHours(typeOfWorkHours); + workReportLine.setEffort(EffortDuration.hours(10)); + + workReport.addWorkReportLine(workReportLine); + workReportDAO.save(workReport); + } + + private void givenTask() { + taskElement = createNiceMock(TaskElement.class); + expect(taskElement.getOrderElement()).andReturn(orderElement) + .anyTimes(); + replay(taskElement); + } + + private void givenBasicExample() { + givenTypeOfWorkHours(); + givenCostCategory(); + givenResource(); + givenOrderElement(); + giveWorkReportType(); + givenWorkReport(); + givenTask(); + } + + @Test + public void basicTest() throws InstanceNotFoundException { + givenBasicExample(); + assertThat(moneyCostCalculator.getMoneyCost(taskElement), + equalTo(new BigDecimal(500).setScale(2))); + } + +}